From 796f784fd1d3554bf80612bea90b577ecace8c9a Mon Sep 17 00:00:00 2001 From: ahmedelkhatib Date: Tue, 21 Jun 2022 14:11:44 +0800 Subject: [PATCH 1/5] Fixed an issue on Android < Q where if the user already successfully connected to the network before, a new connection request with a wrong password would succeed. (Tested on Android 8.1 OnePlus 5T) Changed the WIFI behavior when disconnecting from an app-created network on Android < Q to be loosely similar to observed behvaior on Android 11 (OnePlus 7 Pro) where the device will try to reconnect to the previously connected network. --- .../com/alternadom/wifiiot/WifiIotPlugin.java | 127 ++++++++++++++---- 1 file changed, 102 insertions(+), 25 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 84df0b47..103f03b4 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -14,7 +14,6 @@ import android.net.NetworkRequest; import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; -import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; @@ -49,6 +48,8 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.Random; +import android.net.wifi.SupplicantState; /** WifiIotPlugin */ public class WifiIotPlugin @@ -73,6 +74,8 @@ public class WifiIotPlugin private List networkSuggestions; private List ssidsToBeRemovedOnExit = new ArrayList(); private List suggestionsToBeRemovedOnExit = new ArrayList<>(); + //last connected network ID from outside the app + private int lastConnectedNetworkId = -1; // Permission request management private boolean requestingPermission = false; @@ -98,20 +101,12 @@ private void initWithActivity(Activity activity) { // cleanup private void cleanup() { - if (!ssidsToBeRemovedOnExit.isEmpty()) { - List wifiConfigList = moWiFi.getConfiguredNetworks(); - for (String ssid : ssidsToBeRemovedOnExit) { - for (WifiConfiguration wifiConfig : wifiConfigList) { - if (wifiConfig.SSID.equals(ssid)) { - moWiFi.removeNetwork(wifiConfig.networkId); - } - } - } - } + removeAddedNetworks(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !suggestionsToBeRemovedOnExit.isEmpty()) { moWiFi.removeNetworkSuggestions(suggestionsToBeRemovedOnExit); } // setting all members to null to avoid memory leaks + lastConnectedNetworkId = -1; channel = null; eventChannel = null; moActivity = null; @@ -120,6 +115,20 @@ private void cleanup() { moWiFiAPManager = null; } + private void removeAddedNetworks() { + if (!ssidsToBeRemovedOnExit.isEmpty()) { + List wifiConfigList = moWiFi.getConfiguredNetworks(); + for (String ssid : ssidsToBeRemovedOnExit) { + for (WifiConfiguration wifiConfig : wifiConfigList) { + if (wifiConfig.SSID.equals(ssid)) { + moWiFi.removeNetwork(wifiConfig.networkId); + } + } + } + } + ssidsToBeRemovedOnExit.clear(); + } + /** Plugin registration. This is used for registering with v1 Android embedding. */ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "wifi_iot"); @@ -968,7 +977,7 @@ private void registerWifiNetwork(final MethodCall poCall, final Result poResult) android.net.wifi.WifiConfiguration conf = generateConfiguration(ssid, bssid, password, security, isHidden); - int updateNetwork = registerWifiNetworkDeprecated(conf); + int updateNetwork = registerWifiNetworkDeprecated(conf, false); if (updateNetwork == -1) { poResult.error("Error", "Error updating network configuration", ""); @@ -1107,6 +1116,10 @@ private void disconnect(Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { //noinspection deprecation disconnected = moWiFi.disconnect(); + if(lastConnectedNetworkId != -1) { + //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID + moWiFi.enableNetwork(lastConnectedNetworkId, true); + } } else { if (networkCallback != null) { final ConnectivityManager connectivityManager = @@ -1186,14 +1199,24 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { List mWifiConfigList = moWiFi.getConfiguredNetworks(); for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { - String comparableSSID = ('"' + prefix_ssid); //Add quotes because wifiConfig.SSID has them - if (wifiConfig.SSID.startsWith(comparableSSID)) { - moWiFi.removeNetwork(wifiConfig.networkId); - moWiFi.saveConfiguration(); - removed = true; - break; + String comparableSSID = ('"' + prefix_ssid + '"'); //Add quotes because wifiConfig.SSID has them + if (wifiConfig.SSID.equals(comparableSSID)) { + Boolean isRemoved = moWiFi.removeNetwork(wifiConfig.networkId); + if(isRemoved) { + moWiFi.saveConfiguration(); + removed = true; + //if the last connected network was our app's network, reset the last connected network + if(wifiConfig.networkId == lastConnectedNetworkId) { + lastConnectedNetworkId = -1; + } + } + //multiple networks with the same SSID could be removed } } + if(lastConnectedNetworkId != -1) { + //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID + moWiFi.enableNetwork(lastConnectedNetworkId, true); + } } // remove network suggestion @@ -1399,7 +1422,7 @@ public void onUnavailable() { } @SuppressWarnings("deprecation") - private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration conf) { + private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration conf, Boolean joinOnce) { int updateNetwork = -1; int registeredNetwork = -1; @@ -1414,7 +1437,43 @@ private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration con || wifiConfig.BSSID.equals(conf.BSSID))) { conf.networkId = wifiConfig.networkId; registeredNetwork = wifiConfig.networkId; - updateNetwork = moWiFi.updateNetwork(conf); + //only try to update the configuration if joinOnce is false + //otherwise use the new add/update method + if(joinOnce == false) { + updateNetwork = moWiFi.updateNetwork(conf); + //required for pre API 26 + moWiFi.saveConfiguration(); + } + //Android 6.0 and higher no longer allows you to update a network that wasn't created + //from our app, nor delete it, nor add a network with the same SSID + //See https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-network + if(updateNetwork == -1) { + //we add some random number to the conf SSID, add that network, then change the SSID + // back to circumvent the issue + String ssid = conf.SSID; + Random random = new Random(System.currentTimeMillis()); + //loop in the rare case that the generated ssid already exists + for (int i = 0; i < 20; i++) { + int randomInteger = random.nextInt(10000); + //create a valid SSID with max length of 32 + String ssidRandomized = ssid+randomInteger; + int ssidRandomizedExtraLength = ssidRandomized.length()-32; + if(ssidRandomizedExtraLength > 0) { + ssidRandomized = ssid.substring(0,ssid.length()-ssidRandomizedExtraLength)+randomInteger; + } + conf.SSID = "\"" + ssidRandomized + "\""; + updateNetwork = moWiFi.addNetwork(conf); // Add my wifi with another name + conf.SSID = ssid; + conf.networkId = updateNetwork; + updateNetwork = moWiFi.updateNetwork(conf); // After my wifi is added with another name, I change it to the desired name + moWiFi.saveConfiguration(); + if(updateNetwork != -1) { + break; + } + } + } + //no need to continue looping + break; } } } @@ -1422,6 +1481,7 @@ private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration con /// If network not already in configured networks add new network if (updateNetwork == -1) { updateNetwork = moWiFi.addNetwork(conf); + conf.networkId = updateNetwork; moWiFi.saveConfiguration(); } @@ -1491,7 +1551,7 @@ private Boolean connectToDeprecated( android.net.wifi.WifiConfiguration conf = generateConfiguration(ssid, bssid, password, security, isHidden); - int updateNetwork = registerWifiNetworkDeprecated(conf); + int updateNetwork = registerWifiNetworkDeprecated(conf, joinOnce); if (updateNetwork == -1) { return false; @@ -1500,6 +1560,9 @@ private Boolean connectToDeprecated( if (joinOnce != null && joinOnce.booleanValue()) { ssidsToBeRemovedOnExit.add(conf.SSID); } + if(lastConnectedNetworkId == -1) { + lastConnectedNetworkId = moWiFi.getConnectionInfo().getNetworkId(); + } boolean disconnect = moWiFi.disconnect(); if (!disconnect) { @@ -1510,24 +1573,38 @@ private Boolean connectToDeprecated( if (!enabled) return false; boolean connected = false; + int networkId = -1; for (int i = 0; i < 20; i++) { WifiInfo currentNet = moWiFi.getConnectionInfo(); - int networkId = currentNet.getNetworkId(); + networkId = currentNet.getNetworkId(); SupplicantState netState = currentNet.getSupplicantState(); // Wait for connection to reach state completed // to discard false positives like auth error if (networkId != -1 && netState == SupplicantState.COMPLETED) { connected = networkId == updateNetwork; - break; + if(connected == true) { + break; + } else { + disconnect = moWiFi.disconnect(); + if (!disconnect) { + break; + } + + enabled = moWiFi.enableNetwork(updateNetwork, true); + break; + } } try { - Thread.sleep(500); + Thread.sleep(1000); } catch (InterruptedException ignored) { break; } } - + if(!connected && lastConnectedNetworkId != -1) { + //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID + moWiFi.enableNetwork(lastConnectedNetworkId, true); + } return connected; } } From d023eaa971ef2ec82d19c593188c9ade2400bce9 Mon Sep 17 00:00:00 2001 From: Harsh Bhikadia Date: Sun, 28 Aug 2022 20:44:12 +0530 Subject: [PATCH 2/5] minor changes --- .../src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 103f03b4..c5e4f8f5 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -1439,7 +1439,7 @@ private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration con registeredNetwork = wifiConfig.networkId; //only try to update the configuration if joinOnce is false //otherwise use the new add/update method - if(joinOnce == false) { + if(joinOnce != null && !joinOnce.booleanValue()) { updateNetwork = moWiFi.updateNetwork(conf); //required for pre API 26 moWiFi.saveConfiguration(); @@ -1583,7 +1583,7 @@ private Boolean connectToDeprecated( // to discard false positives like auth error if (networkId != -1 && netState == SupplicantState.COMPLETED) { connected = networkId == updateNetwork; - if(connected == true) { + if(connected) { break; } else { disconnect = moWiFi.disconnect(); From 6338aa84dd7cce675e159e2b346990f60c9bd65f Mon Sep 17 00:00:00 2001 From: Harsh Bhikadia Date: Sun, 28 Aug 2022 20:46:12 +0530 Subject: [PATCH 3/5] formatting --- .../com/alternadom/wifiiot/WifiIotPlugin.java | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index c5e4f8f5..80c4c22e 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -14,6 +14,7 @@ import android.net.NetworkRequest; import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; +import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; @@ -45,11 +46,10 @@ import io.flutter.view.FlutterNativeView; import java.util.ArrayList; import java.util.List; +import java.util.Random; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.Random; -import android.net.wifi.SupplicantState; /** WifiIotPlugin */ public class WifiIotPlugin @@ -1116,7 +1116,7 @@ private void disconnect(Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { //noinspection deprecation disconnected = moWiFi.disconnect(); - if(lastConnectedNetworkId != -1) { + if (lastConnectedNetworkId != -1) { //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID moWiFi.enableNetwork(lastConnectedNetworkId, true); } @@ -1199,21 +1199,22 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { List mWifiConfigList = moWiFi.getConfiguredNetworks(); for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { - String comparableSSID = ('"' + prefix_ssid + '"'); //Add quotes because wifiConfig.SSID has them + String comparableSSID = + ('"' + prefix_ssid + '"'); //Add quotes because wifiConfig.SSID has them if (wifiConfig.SSID.equals(comparableSSID)) { Boolean isRemoved = moWiFi.removeNetwork(wifiConfig.networkId); - if(isRemoved) { + if (isRemoved) { moWiFi.saveConfiguration(); removed = true; //if the last connected network was our app's network, reset the last connected network - if(wifiConfig.networkId == lastConnectedNetworkId) { + if (wifiConfig.networkId == lastConnectedNetworkId) { lastConnectedNetworkId = -1; } } //multiple networks with the same SSID could be removed } } - if(lastConnectedNetworkId != -1) { + if (lastConnectedNetworkId != -1) { //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID moWiFi.enableNetwork(lastConnectedNetworkId, true); } @@ -1422,7 +1423,8 @@ public void onUnavailable() { } @SuppressWarnings("deprecation") - private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration conf, Boolean joinOnce) { + private int registerWifiNetworkDeprecated( + android.net.wifi.WifiConfiguration conf, Boolean joinOnce) { int updateNetwork = -1; int registeredNetwork = -1; @@ -1439,7 +1441,7 @@ private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration con registeredNetwork = wifiConfig.networkId; //only try to update the configuration if joinOnce is false //otherwise use the new add/update method - if(joinOnce != null && !joinOnce.booleanValue()) { + if (joinOnce != null && !joinOnce.booleanValue()) { updateNetwork = moWiFi.updateNetwork(conf); //required for pre API 26 moWiFi.saveConfiguration(); @@ -1447,7 +1449,7 @@ private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration con //Android 6.0 and higher no longer allows you to update a network that wasn't created //from our app, nor delete it, nor add a network with the same SSID //See https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-network - if(updateNetwork == -1) { + if (updateNetwork == -1) { //we add some random number to the conf SSID, add that network, then change the SSID // back to circumvent the issue String ssid = conf.SSID; @@ -1456,18 +1458,21 @@ private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration con for (int i = 0; i < 20; i++) { int randomInteger = random.nextInt(10000); //create a valid SSID with max length of 32 - String ssidRandomized = ssid+randomInteger; - int ssidRandomizedExtraLength = ssidRandomized.length()-32; - if(ssidRandomizedExtraLength > 0) { - ssidRandomized = ssid.substring(0,ssid.length()-ssidRandomizedExtraLength)+randomInteger; + String ssidRandomized = ssid + randomInteger; + int ssidRandomizedExtraLength = ssidRandomized.length() - 32; + if (ssidRandomizedExtraLength > 0) { + ssidRandomized = + ssid.substring(0, ssid.length() - ssidRandomizedExtraLength) + randomInteger; } conf.SSID = "\"" + ssidRandomized + "\""; updateNetwork = moWiFi.addNetwork(conf); // Add my wifi with another name conf.SSID = ssid; conf.networkId = updateNetwork; - updateNetwork = moWiFi.updateNetwork(conf); // After my wifi is added with another name, I change it to the desired name + updateNetwork = + moWiFi.updateNetwork( + conf); // After my wifi is added with another name, I change it to the desired name moWiFi.saveConfiguration(); - if(updateNetwork != -1) { + if (updateNetwork != -1) { break; } } @@ -1560,7 +1565,7 @@ private Boolean connectToDeprecated( if (joinOnce != null && joinOnce.booleanValue()) { ssidsToBeRemovedOnExit.add(conf.SSID); } - if(lastConnectedNetworkId == -1) { + if (lastConnectedNetworkId == -1) { lastConnectedNetworkId = moWiFi.getConnectionInfo().getNetworkId(); } @@ -1583,7 +1588,7 @@ private Boolean connectToDeprecated( // to discard false positives like auth error if (networkId != -1 && netState == SupplicantState.COMPLETED) { connected = networkId == updateNetwork; - if(connected) { + if (connected) { break; } else { disconnect = moWiFi.disconnect(); @@ -1601,7 +1606,7 @@ private Boolean connectToDeprecated( break; } } - if(!connected && lastConnectedNetworkId != -1) { + if (!connected && lastConnectedNetworkId != -1) { //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID moWiFi.enableNetwork(lastConnectedNetworkId, true); } From f70e7202cd19020f83014166db33ad1d040ed998 Mon Sep 17 00:00:00 2001 From: Hazem Date: Mon, 3 Nov 2025 15:10:39 +0200 Subject: [PATCH 4/5] declare namespace --- packages/wifi_iot/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wifi_iot/android/build.gradle b/packages/wifi_iot/android/build.gradle index 818a711b..2df3f676 100644 --- a/packages/wifi_iot/android/build.gradle +++ b/packages/wifi_iot/android/build.gradle @@ -23,7 +23,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 30 - + namespace "com.alternadom.wifiiot" defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From 0ca45c36bd7963f88381916308d6ca31597acda9 Mon Sep 17 00:00:00 2001 From: Hazem Date: Mon, 3 Nov 2025 15:28:18 +0200 Subject: [PATCH 5/5] migrate to embedding v2 --- .../com/alternadom/wifiiot/WifiIotPlugin.java | 1739 ++++++++--------- 1 file changed, 860 insertions(+), 879 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 80c4c22e..e7638757 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -26,12 +26,15 @@ import android.os.Looper; import android.provider.Settings; import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; + import info.whitebyte.hotspotmanager.ClientScanResult; import info.whitebyte.hotspotmanager.FinishScanListener; import info.whitebyte.hotspotmanager.WIFI_AP_STATE; import info.whitebyte.hotspotmanager.WifiApManager; + import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -40,26 +43,24 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener; -import io.flutter.plugin.common.PluginRegistry.ViewDestroyListener; -import io.flutter.view.FlutterNativeView; + import java.util.ArrayList; import java.util.List; import java.util.Random; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -/** WifiIotPlugin */ +/** WifiIotPlugin (Flutter embedding v2) */ public class WifiIotPlugin - implements FlutterPlugin, + implements FlutterPlugin, ActivityAware, MethodCallHandler, EventChannel.StreamHandler, RequestPermissionsResultListener { - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity + private MethodChannel channel; private EventChannel eventChannel; @@ -72,19 +73,18 @@ public class WifiIotPlugin private WIFI_AP_STATE localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; private ConnectivityManager.NetworkCallback networkCallback; private List networkSuggestions; - private List ssidsToBeRemovedOnExit = new ArrayList(); - private List suggestionsToBeRemovedOnExit = new ArrayList<>(); - //last connected network ID from outside the app + private final List ssidsToBeRemovedOnExit = new ArrayList<>(); + private final List suggestionsToBeRemovedOnExit = new ArrayList<>(); + // last connected network ID from outside the app private int lastConnectedNetworkId = -1; // Permission request management private boolean requestingPermission = false; private Result permissionRequestResultCallback = null; - private ArrayList permissionRequestCookie = new ArrayList<>(); + private final ArrayList permissionRequestCookie = new ArrayList<>(); private static final int PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST = 65655435; private static final int PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN = 65655436; - private static final int PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT = - 65655437; + private static final int PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT = 65655437; private static final int PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED = 65655438; // initialize members of this class with Context @@ -129,34 +129,12 @@ private void removeAddedNetworks() { ssidsToBeRemovedOnExit.clear(); } - /** Plugin registration. This is used for registering with v1 Android embedding. */ - public static void registerWith(Registrar registrar) { - final MethodChannel channel = new MethodChannel(registrar.messenger(), "wifi_iot"); - final EventChannel eventChannel = - new EventChannel(registrar.messenger(), "plugins.wififlutter.io/wifi_scan"); - final WifiIotPlugin wifiIotPlugin = new WifiIotPlugin(); - wifiIotPlugin.initWithActivity(registrar.activity()); - wifiIotPlugin.initWithContext(registrar.activeContext()); - eventChannel.setStreamHandler(wifiIotPlugin); - channel.setMethodCallHandler(wifiIotPlugin); - - registrar.addViewDestroyListener( - new ViewDestroyListener() { - @Override - public boolean onViewDestroy(FlutterNativeView view) { - wifiIotPlugin.cleanup(); - return false; - } - }); - registrar.addRequestPermissionsResultListener(wifiIotPlugin); - } - + // ---------- Embedding v2 wiring ---------- @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { // initialize method and event channel and set handlers channel = new MethodChannel(binding.getBinaryMessenger(), "wifi_iot"); - eventChannel = - new EventChannel(binding.getBinaryMessenger(), "plugins.wififlutter.io/wifi_scan"); + eventChannel = new EventChannel(binding.getBinaryMessenger(), "plugins.wififlutter.io/wifi_scan"); channel.setMethodCallHandler(this); eventChannel.setStreamHandler(this); @@ -200,18 +178,19 @@ public void onDetachedFromActivity() { moActivity = null; } + // ---------- Permissions ---------- @Override public boolean onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { + int requestCode, String[] permissions, int[] grantResults) { final boolean wasPermissionGranted = - grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; + grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; switch (requestCode) { case PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST: if (wasPermissionGranted) { _loadWifiList(permissionRequestResultCallback); } else { permissionRequestResultCallback.error( - "WifiIotPlugin.Permission", "Fine location permission denied", null); + "WifiIotPlugin.Permission", "Fine location permission denied", null); } requestingPermission = false; return true; @@ -219,7 +198,7 @@ public boolean onRequestPermissionsResult( case PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN: if (wasPermissionGranted) { final EventChannel.EventSink eventSink = - (EventChannel.EventSink) permissionRequestCookie.get(0); + (EventChannel.EventSink) permissionRequestCookie.get(0); _onListen(eventSink); } requestingPermission = false; @@ -231,7 +210,7 @@ public boolean onRequestPermissionsResult( _findAndConnect(poCall, permissionRequestResultCallback); } else { permissionRequestResultCallback.error( - "WifiIotPlugin.Permission", "Fine location permission denied", null); + "WifiIotPlugin.Permission", "Fine location permission denied", null); } requestingPermission = false; return true; @@ -241,7 +220,7 @@ public boolean onRequestPermissionsResult( _isConnected(permissionRequestResultCallback); } else { permissionRequestResultCallback.error( - "WifiIotPlugin.Permission", "Network state permission denied", null); + "WifiIotPlugin.Permission", "Network state permission denied", null); } requestingPermission = false; return true; @@ -250,6 +229,7 @@ public boolean onRequestPermissionsResult( return false; } + // ---------- Method channel ---------- @Override public void onMethodCall(MethodCall poCall, Result poResult) { switch (poCall.method) { @@ -303,9 +283,9 @@ public void onMethodCall(MethodCall poCall, Result poResult) { isRegisteredWifiNetwork(poCall, poResult); else poResult.error( - "Error", - "isRegisteredWifiNetwork not supported for Android SDK " + Build.VERSION.SDK_INT, - null); + "Error", + "isRegisteredWifiNetwork not supported for Android SDK " + Build.VERSION.SDK_INT, + null); break; case "isWiFiAPEnabled": isWiFiAPEnabled(poResult); @@ -346,750 +326,413 @@ public void onMethodCall(MethodCall poCall, Result poResult) { } } - /** - * The network's SSID. Can either be an ASCII string, which must be enclosed in double quotation - * marks (e.g., {@code "MyNetwork"}), or a string of hex digits, which are not enclosed in quotes - * (e.g., {@code 01a243f405}). - */ - private void getWiFiAPSSID(Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - - if (oWiFiConfig != null && oWiFiConfig.SSID != null) { - poResult.success(oWiFiConfig.SSID); + // ---------- Event channel ---------- + @Override + public void onListen(Object o, EventChannel.EventSink eventSink) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + if (requestingPermission) { return; } - - poResult.error("Exception [getWiFiAPSSID]", "SSID not found", null); + requestingPermission = true; + permissionRequestCookie.clear(); + permissionRequestCookie.add(eventSink); + moActivity.requestPermissions( + new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, + PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN); + // actual call will be handled in [onRequestPermissionsResult] } else { - if (apReservation != null) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); - if (wifiConfiguration != null) { - poResult.success(wifiConfiguration.SSID); - } else { - poResult.error( - "Exception [getWiFiAPSSID]", - "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", - null); - } - } else { - SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.getSsid()); - } - } else { - poResult.error("Exception [getWiFiAPSSID]", "Hotspot is not enabled.", null); - } + _onListen(eventSink); } } - private void setWiFiAPSSID(MethodCall poCall, Result poResult) { - String sAPSSID = poCall.argument("ssid"); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - - oWiFiConfig.SSID = sAPSSID; - - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - - poResult.success(null); - } else { - poResult.error( - "Exception [setWiFiAPSSID]", - "Setting SSID name is not supported on API level >= 26", - null); + @Override + public void onCancel(Object o) { + if (receiver != null) { + moContext.unregisterReceiver(receiver); + receiver = null; } } - /** - * This is a network that does not broadcast its SSID, so an SSID-specific probe request must be - * used for scans. - */ - private void isSSIDHidden(Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + private void _onListen(EventChannel.EventSink eventSink) { + receiver = createReceiver(eventSink); + moContext.registerReceiver( + receiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + } - if (oWiFiConfig != null && oWiFiConfig.hiddenSSID) { - poResult.success(oWiFiConfig.hiddenSSID); - return; + private BroadcastReceiver createReceiver(final EventChannel.EventSink eventSink) { + return new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + eventSink.success(handleNetworkScanResult().toString()); } + }; + } - poResult.error("Exception [isSSIDHidden]", "Wifi AP not Supported", null); - } else { - if (apReservation != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.isHiddenSsid()); - } else { - WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); - if (wifiConfiguration != null) { - poResult.success(wifiConfiguration.hiddenSSID); + JSONArray handleNetworkScanResult() { + List results = moWiFi.getScanResults(); + JSONArray wifiArray = new JSONArray(); + + try { + for (ScanResult result : results) { + JSONObject wifiObject = new JSONObject(); + if (!result.SSID.equals("")) { + + wifiObject.put("SSID", result.SSID); + wifiObject.put("BSSID", result.BSSID); + wifiObject.put("capabilities", result.capabilities); + wifiObject.put("frequency", result.frequency); + wifiObject.put("level", result.level); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + wifiObject.put("timestamp", result.timestamp); } else { - poResult.error( - "Exception [isSSIDHidden]", - "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", - null); + wifiObject.put("timestamp", 0); } + /// Other fields not added + //wifiObject.put("operatorFriendlyName", result.operatorFriendlyName); + //wifiObject.put("venueName", result.venueName); + //wifiObject.put("centerFreq0", result.centerFreq0); + //wifiObject.put("centerFreq1", result.centerFreq1); + //wifiObject.put("channelWidth", result.channelWidth); + + wifiArray.put(wifiObject); } - } else { - poResult.error("Exception [isSSIDHidden]", "Hotspot is not enabled.", null); } + } catch (JSONException e) { + e.printStackTrace(); + } finally { + return wifiArray; + } + } + + /// Method to load wifi list into string via Callback. Returns a stringified JSONArray + private void loadWifiList(final Result poResult) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + if (requestingPermission) { + poResult.error( + "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); + return; + } + requestingPermission = true; + permissionRequestResultCallback = poResult; + moActivity.requestPermissions( + new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, + PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST); + // actual call will be handled in [onRequestPermissionsResult] + } else { + _loadWifiList(poResult); } } - private void setSSIDHidden(MethodCall poCall, Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - boolean isSSIDHidden = poCall.argument("hidden"); + private void _loadWifiList(final Result poResult) { + try { + moWiFi.startScan(); + poResult.success(handleNetworkScanResult().toString()); + } catch (Exception e) { + poResult.error("Exception", e.getMessage(), null); + } + } - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + /// Method to force wifi usage if the user needs to send requests via wifi + /// if it does not have internet connection. Useful for IoT applications, when + /// the app needs to communicate and send requests to a device that have no + /// internet connection via wifi. - oWiFiConfig.hiddenSSID = isSSIDHidden; + /// Receives a boolean to enable forceWifiUsage if true, and disable if false. + /// Is important to enable only when communicating with the device via wifi + /// and remember to disable it when disconnecting from device. + private void forceWifiUsage(final MethodCall poCall, final Result poResult) { + boolean useWifi = poCall.argument("useWifi"); - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); + final ConnectivityManager manager = + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - poResult.success(null); - } else { - poResult.error( - "Exception [setSSIDHidden]", - "Setting SSID visibility is not supported on API level >= 26", - null); + boolean success = true; + boolean shouldReply = true; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP && manager != null) { + if (useWifi) { + NetworkRequest.Builder builder; + builder = new NetworkRequest.Builder(); + /// set the transport type do WIFI + builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + shouldReply = false; + manager.requestNetwork( + builder.build(), + new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + super.onAvailable(network); + boolean success = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + success = manager.bindProcessToNetwork(network); + + } else { + success = ConnectivityManager.setProcessDefaultNetwork(network); + } + manager.unregisterNetworkCallback(this); + final boolean result = success; + final Handler handler = new Handler(Looper.getMainLooper()); + handler.post( + new Runnable() { + @Override + public void run() { + poResult.success(result); + } + }); + } + }); + + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + success = manager.bindProcessToNetwork(null); + } else { + success = ConnectivityManager.setProcessDefaultNetwork(null); + } + } + } + if (shouldReply) { + poResult.success(success); } } - /** - * Pre-shared key for use with WPA-PSK. Either an ASCII string enclosed in double quotation marks - * (e.g., {@code "abcdefghij"} for PSK passphrase or a string of 64 hex digits for raw PSK. - * - *

When the value of this key is read, the actual key is not returned, just a "*" if the key - * has a value, or the null string otherwise. - */ - private void getWiFiAPPreSharedKey(Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + /// Method to check if wifi is enabled + private void isEnabled(Result poResult) { + poResult.success(moWiFi.isWifiEnabled()); + } - if (oWiFiConfig != null && oWiFiConfig.preSharedKey != null) { - poResult.success(oWiFiConfig.preSharedKey); - return; - } + /// Method to connect/disconnect wifi service + private void setEnabled(MethodCall poCall, Result poResult) { + Boolean enabled = poCall.argument("state"); + Boolean shouldOpenSettings = poCall.argument("shouldOpenSettings"); - poResult.error("Exception", "Wifi AP not Supported", null); - } else { - if (apReservation != null) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); - if (wifiConfiguration != null) { - poResult.success(wifiConfiguration.preSharedKey); - } else { - poResult.error( - "Exception [getWiFiAPPreSharedKey]", - "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", - null); - } + // Enable or Disable WiFi programmatically + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + moWiFi.setWifiEnabled(enabled); + } + // Whether to open native WiFi settings or not + else { + if (shouldOpenSettings != null) { + if (shouldOpenSettings) { + Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + this.moContext.startActivity(intent); } else { - SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.getPassphrase()); + moWiFi.setWifiEnabled(enabled); } } else { - poResult.error("Exception [getWiFiAPPreSharedKey]", "Hotspot is not enabled.", null); + Log.e( + WifiIotPlugin.class.getSimpleName(), "Error `setEnabled`: shouldOpenSettings is null."); } } - } - private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - String sPreSharedKey = poCall.argument("preSharedKey"); - - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + poResult.success(null); + } - oWiFiConfig.preSharedKey = sPreSharedKey; - - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); + private void connect(final MethodCall poCall, final Result poResult) { + new Thread() { + public void run() { + String ssid = poCall.argument("ssid"); + String bssid = poCall.argument("bssid"); + String password = poCall.argument("password"); + String security = poCall.argument("security"); + Boolean joinOnce = poCall.argument("join_once"); + Boolean withInternet = poCall.argument("with_internet"); + Boolean isHidden = poCall.argument("is_hidden"); - poResult.success(null); - } else { - poResult.error( - "Exception [setWiFiAPPreSharedKey]", - "Setting WiFi password is not supported on API level >= 26", - null); - } + connectTo(poResult, ssid, bssid, password, security, joinOnce, withInternet, isHidden); + } + }.start(); } - /** - * Gets a list of the clients connected to the Hotspot *** getClientList : param onlyReachables - * {@code false} if the list should contain unreachable (probably disconnected) clients, {@code - * true} otherwise param reachableTimeout Reachable Timout in miliseconds, 300 is default param - * finishListener, Interface called when the scan method finishes - */ - private void getClientList(MethodCall poCall, final Result poResult) { - Boolean onlyReachables = false; - if (poCall.argument("onlyReachables") != null) { - onlyReachables = poCall.argument("onlyReachables"); - } - - Integer reachableTimeout = 300; - if (poCall.argument("reachableTimeout") != null) { - reachableTimeout = poCall.argument("reachableTimeout"); + /// Transform a string based bssid into a MacAdress. + /// Return null in case of error. + @RequiresApi(Build.VERSION_CODES.P) + private static MacAddress macAddressFromBssid(String bssid) { + if (bssid == null) { + return null; } - final Boolean finalOnlyReachables = onlyReachables; - FinishScanListener oFinishScanListener = - new FinishScanListener() { - @Override - public void onFinishScan(final ArrayList clients) { - try { - JSONArray clientArray = new JSONArray(); - - for (ClientScanResult client : clients) { - JSONObject clientObject = new JSONObject(); - - Boolean clientIsReachable = client.isReachable(); - Boolean shouldReturnCurrentClient = true; - if (finalOnlyReachables.booleanValue()) { - if (!clientIsReachable.booleanValue()) { - shouldReturnCurrentClient = Boolean.valueOf(false); - } - } - if (shouldReturnCurrentClient.booleanValue()) { - try { - clientObject.put("IPAddr", client.getIpAddr()); - clientObject.put("HWAddr", client.getHWAddr()); - clientObject.put("Device", client.getDevice()); - clientObject.put("isReachable", client.isReachable()); - } catch (JSONException e) { - poResult.error("Exception", e.getMessage(), null); - } - clientArray.put(clientObject); - } - } - poResult.success(clientArray.toString()); - } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); - } - } - }; - - if (reachableTimeout != null) { - moWiFiAPManager.getClientList(onlyReachables, reachableTimeout, oFinishScanListener); - } else { - moWiFiAPManager.getClientList(onlyReachables, oFinishScanListener); + try { + return MacAddress.fromString(bssid); + } catch (IllegalArgumentException invalidRepresentation) { + Log.e( + WifiIotPlugin.class.getSimpleName(), + "Mac address parsing failed for bssid: " + bssid, + invalidRepresentation); + return null; } } /** - * Return whether Wi-Fi AP is enabled or disabled. *** isWifiApEnabled : return {@code true} if - * Wi-Fi AP is enabled + * Registers a wifi network in the device wireless networks For API >= 30 uses intent to + * permanently store such network in user configuration For API <= 29 uses deprecated functions + * that manipulate directly *** registerWifiNetwork : param ssid, SSID to register param password, + * passphrase to use param security, security mode (WPA or null) to use return {@code true} if the + * operation succeeds, {@code false} otherwise */ - private void isWiFiAPEnabled(Result poResult) { + private void registerWifiNetwork(final MethodCall poCall, final Result poResult) { + String ssid = poCall.argument("ssid"); + String bssid = poCall.argument("bssid"); + String password = poCall.argument("password"); + String security = poCall.argument("security"); + Boolean isHidden = poCall.argument("is_hidden"); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - try { - poResult.success(moWiFiAPManager.isWifiApEnabled()); - } catch (SecurityException e) { - Log.e(WifiIotPlugin.class.getSimpleName(), e.getMessage(), null); - poResult.error("Exception [isWiFiAPEnabled]", e.getMessage(), null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final WifiNetworkSuggestion.Builder suggestedNet = new WifiNetworkSuggestion.Builder(); + suggestedNet.setSsid(ssid); + suggestedNet.setIsHiddenSsid(isHidden != null ? isHidden : false); + if (bssid != null) { + final MacAddress macAddress = macAddressFromBssid(bssid); + if (macAddress == null) { + poResult.error("Error", "Invalid BSSID representation", ""); + return; + } + suggestedNet.setBssid(macAddress); } - } else { - poResult.success(apReservation != null); - } - } - /** - * Start AccessPoint mode with the specified configuration. If the radio is already running in AP - * mode, update the new configuration Note that starting in access point mode disables station - * mode operation *** setWifiApEnabled : param wifiConfig SSID, security and channel details as - * part of WifiConfiguration return {@code true} if the operation succeeds, {@code false} - * otherwise - */ - private void setWiFiAPEnabled(MethodCall poCall, final Result poResult) { - boolean enabled = poCall.argument("state"); + if (security != null && security.toUpperCase().equals("WPA")) { + suggestedNet.setWpa2Passphrase(password); + } else if (security != null && security.toUpperCase().equals("WEP")) { + // WEP is not supported + poResult.error( + "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, ""); + return; + } - /** Using LocalOnlyHotspotCallback when setting WiFi AP state on API level >= 29 */ - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - final boolean result = moWiFiAPManager.setWifiApEnabled(null, enabled); - poResult.success(result); + final ArrayList suggestionsList = + new ArrayList(); + suggestionsList.add(suggestedNet.build()); + + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + android.provider.Settings.EXTRA_WIFI_NETWORK_LIST, suggestionsList); + Intent intent = new Intent(android.provider.Settings.ACTION_WIFI_ADD_NETWORKS); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + moContext.startActivity(intent); + + poResult.success(null); } else { - if (enabled) { - localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLING; - moWiFi.startLocalOnlyHotspot( - new WifiManager.LocalOnlyHotspotCallback() { - @Override - public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { - super.onStarted(reservation); - apReservation = reservation; - localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLED; - poResult.success(true); - } + // Deprecated version + android.net.wifi.WifiConfiguration conf = + generateConfiguration(ssid, bssid, password, security, isHidden); - @Override - public void onStopped() { - super.onStopped(); - if (apReservation != null) { - apReservation.close(); - } - apReservation = null; - localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; - Log.d(WifiIotPlugin.class.getSimpleName(), "LocalHotspot Stopped."); - } + int updateNetwork = registerWifiNetworkDeprecated(conf, false); - @Override - public void onFailed(int reason) { - super.onFailed(reason); - if (apReservation != null) { - apReservation.close(); - } - apReservation = null; - localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_FAILED; - Log.d( - WifiIotPlugin.class.getSimpleName(), - "LocalHotspot failed with code: " + String.valueOf(reason)); - poResult.success(false); - } - }, - new Handler()); + if (updateNetwork == -1) { + poResult.error("Error", "Error updating network configuration", ""); } else { - localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLING; - if (apReservation != null) { - apReservation.close(); - apReservation = null; - poResult.success(true); - } else { - Log.e( - WifiIotPlugin.class.getSimpleName(), "Can't disable WiFi AP, apReservation is null."); - poResult.success(false); - } - localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; + poResult.success(null); } } } - /** - * Show write permission settings page to user Depending on Android version and application these - * may be needed to perform certain WiFi configurations that require WRITE_SETTINGS which require - * a double opt-in, not just presence in manifest. *** showWritePermissionSettings : param boolean - * force, if true shows always, if false only if permissions are not already granted - */ - private void showWritePermissionSettings(MethodCall poCall, Result poResult) { - boolean force = poCall.argument("force"); - moWiFiAPManager.showWritePermissionSettings(force); - poResult.success(null); - } - - /** Gets the Wi-Fi enabled state. *** getWifiApState : return {link WIFI_AP_STATE} */ - private void getWiFiAPState(Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - poResult.success(moWiFiAPManager.getWifiApState().ordinal()); - } else { - poResult.success(localOnlyHotspotState); - } - } - - @Override - public void onListen(Object o, EventChannel.EventSink eventSink) { + /// Send the ssid and password of a Wifi network into this to connect to the network. + /// Example: wifi.findAndConnect(ssid, password); + /// After 10 seconds, a post telling you whether you are connected will pop up. + /// Callback returns true if ssid is in the range + private void findAndConnect(final MethodCall poCall, final Result poResult) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (requestingPermission) { + poResult.error( + "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); return; } requestingPermission = true; + permissionRequestResultCallback = poResult; permissionRequestCookie.clear(); - permissionRequestCookie.add(eventSink); + permissionRequestCookie.add(poCall); moActivity.requestPermissions( - new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, - PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN); + new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, + PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT); // actual call will be handled in [onRequestPermissionsResult] } else { - _onListen(eventSink); + _findAndConnect(poCall, poResult); } } - private void _onListen(EventChannel.EventSink eventSink) { - receiver = createReceiver(eventSink); - moContext.registerReceiver( - receiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + private void _findAndConnect(final MethodCall poCall, final Result poResult) { + new Thread() { + public void run() { + String ssid = poCall.argument("ssid"); + String bssid = poCall.argument("bssid"); + String password = poCall.argument("password"); + Boolean joinOnce = poCall.argument("join_once"); + Boolean withInternet = poCall.argument("with_internet"); + + String security = null; + List results = moWiFi.getScanResults(); + for (ScanResult result : results) { + String resultString = "" + result.SSID; + if (ssid.equals(resultString) + && (result.BSSID == null || bssid == null || result.BSSID.equals(bssid))) { + security = getSecurityType(result); + if (bssid == null) { + bssid = result.BSSID; + } + } + } + + connectTo(poResult, ssid, bssid, password, security, joinOnce, withInternet, false); + } + }.start(); } - @Override - public void onCancel(Object o) { - if (receiver != null) { - moContext.unregisterReceiver(receiver); - receiver = null; + private static String getSecurityType(ScanResult scanResult) { + String capabilities = scanResult.capabilities; + + if (capabilities.contains("WPA") + || capabilities.contains("WPA2") + || capabilities.contains("WPA/WPA2 PSK")) { + return "WPA"; + } else if (capabilities.contains("WEP")) { + return "WEP"; + } else { + return null; } } - private BroadcastReceiver createReceiver(final EventChannel.EventSink eventSink) { - return new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - eventSink.success(handleNetworkScanResult().toString()); + /// Use this method to check if the device is currently connected to Wifi. + private void isConnected(Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + isConnectedDeprecated(poResult); + } else { + if (moContext.checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) + != PackageManager.PERMISSION_GRANTED) { + if (requestingPermission) { + poResult.error( + "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); + return; + } + requestingPermission = true; + permissionRequestResultCallback = poResult; + moActivity.requestPermissions( + new String[] {Manifest.permission.ACCESS_NETWORK_STATE}, + PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED); + // actual call will be handled in [onRequestPermissionsResult] + } else { + _isConnected(poResult); } - }; - } - - JSONArray handleNetworkScanResult() { - List results = moWiFi.getScanResults(); - JSONArray wifiArray = new JSONArray(); - - try { - for (ScanResult result : results) { - JSONObject wifiObject = new JSONObject(); - if (!result.SSID.equals("")) { - - wifiObject.put("SSID", result.SSID); - wifiObject.put("BSSID", result.BSSID); - wifiObject.put("capabilities", result.capabilities); - wifiObject.put("frequency", result.frequency); - wifiObject.put("level", result.level); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - wifiObject.put("timestamp", result.timestamp); - } else { - wifiObject.put("timestamp", 0); - } - /// Other fields not added - //wifiObject.put("operatorFriendlyName", result.operatorFriendlyName); - //wifiObject.put("venueName", result.venueName); - //wifiObject.put("centerFreq0", result.centerFreq0); - //wifiObject.put("centerFreq1", result.centerFreq1); - //wifiObject.put("channelWidth", result.channelWidth); - - wifiArray.put(wifiObject); - } - } - } catch (JSONException e) { - e.printStackTrace(); - } finally { - return wifiArray; - } - } - - /// Method to load wifi list into string via Callback. Returns a stringified JSONArray - private void loadWifiList(final Result poResult) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - if (requestingPermission) { - poResult.error( - "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); - return; - } - requestingPermission = true; - permissionRequestResultCallback = poResult; - moActivity.requestPermissions( - new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, - PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST); - // actual call will be handled in [onRequestPermissionsResult] - } else { - _loadWifiList(poResult); - } - } - - private void _loadWifiList(final Result poResult) { - try { - moWiFi.startScan(); - poResult.success(handleNetworkScanResult().toString()); - } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); - } - } - - /// Method to force wifi usage if the user needs to send requests via wifi - /// if it does not have internet connection. Useful for IoT applications, when - /// the app needs to communicate and send requests to a device that have no - /// internet connection via wifi. - - /// Receives a boolean to enable forceWifiUsage if true, and disable if false. - /// Is important to enable only when communicating with the device via wifi - /// and remember to disable it when disconnecting from device. - private void forceWifiUsage(final MethodCall poCall, final Result poResult) { - boolean useWifi = poCall.argument("useWifi"); - - final ConnectivityManager manager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - - boolean success = true; - boolean shouldReply = true; - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP && manager != null) { - if (useWifi) { - NetworkRequest.Builder builder; - builder = new NetworkRequest.Builder(); - /// set the transport type do WIFI - builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); - shouldReply = false; - manager.requestNetwork( - builder.build(), - new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(Network network) { - super.onAvailable(network); - boolean success = false; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - success = manager.bindProcessToNetwork(network); - - } else { - success = ConnectivityManager.setProcessDefaultNetwork(network); - } - manager.unregisterNetworkCallback(this); - final boolean result = success; - final Handler handler = new Handler(Looper.getMainLooper()); - handler.post( - new Runnable() { - @Override - public void run() { - poResult.success(result); - } - }); - } - }); - - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - success = manager.bindProcessToNetwork(null); - } else { - success = ConnectivityManager.setProcessDefaultNetwork(null); - } - } - } - if (shouldReply) { - poResult.success(success); - } - } - - /// Method to check if wifi is enabled - private void isEnabled(Result poResult) { - poResult.success(moWiFi.isWifiEnabled()); - } - - /// Method to connect/disconnect wifi service - private void setEnabled(MethodCall poCall, Result poResult) { - Boolean enabled = poCall.argument("state"); - Boolean shouldOpenSettings = poCall.argument("shouldOpenSettings"); - - // Enable or Disable WiFi programmatically - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - moWiFi.setWifiEnabled(enabled); - } - // Whether to open native WiFi settings or not - else { - if (shouldOpenSettings != null) { - if (shouldOpenSettings) { - Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - this.moContext.startActivity(intent); - } else { - moWiFi.setWifiEnabled(enabled); - } - } else { - Log.e( - WifiIotPlugin.class.getSimpleName(), "Error `setEnabled`: shouldOpenSettings is null."); - } - } - - poResult.success(null); - } - - private void connect(final MethodCall poCall, final Result poResult) { - new Thread() { - public void run() { - String ssid = poCall.argument("ssid"); - String bssid = poCall.argument("bssid"); - String password = poCall.argument("password"); - String security = poCall.argument("security"); - Boolean joinOnce = poCall.argument("join_once"); - Boolean withInternet = poCall.argument("with_internet"); - Boolean isHidden = poCall.argument("is_hidden"); - - connectTo(poResult, ssid, bssid, password, security, joinOnce, withInternet, isHidden); - } - }.start(); - } - - /// Transform a string based bssid into a MacAdress. - /// Return null in case of error. - @RequiresApi(Build.VERSION_CODES.P) - private static MacAddress macAddressFromBssid(String bssid) { - if (bssid == null) { - return null; - } - - try { - return MacAddress.fromString(bssid); - } catch (IllegalArgumentException invalidRepresentation) { - Log.e( - WifiIotPlugin.class.getSimpleName(), - "Mac address parsing failed for bssid: " + bssid, - invalidRepresentation); - return null; - } - } - - /** - * Registers a wifi network in the device wireless networks For API >= 30 uses intent to - * permanently store such network in user configuration For API <= 29 uses deprecated functions - * that manipulate directly *** registerWifiNetwork : param ssid, SSID to register param password, - * passphrase to use param security, security mode (WPA or null) to use return {@code true} if the - * operation succeeds, {@code false} otherwise - */ - private void registerWifiNetwork(final MethodCall poCall, final Result poResult) { - String ssid = poCall.argument("ssid"); - String bssid = poCall.argument("bssid"); - String password = poCall.argument("password"); - String security = poCall.argument("security"); - Boolean isHidden = poCall.argument("is_hidden"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - final WifiNetworkSuggestion.Builder suggestedNet = new WifiNetworkSuggestion.Builder(); - suggestedNet.setSsid(ssid); - suggestedNet.setIsHiddenSsid(isHidden != null ? isHidden : false); - if (bssid != null) { - final MacAddress macAddress = macAddressFromBssid(bssid); - if (macAddress == null) { - poResult.error("Error", "Invalid BSSID representation", ""); - return; - } - suggestedNet.setBssid(macAddress); - } - - if (security != null && security.toUpperCase().equals("WPA")) { - suggestedNet.setWpa2Passphrase(password); - } else if (security != null && security.toUpperCase().equals("WEP")) { - // WEP is not supported - poResult.error( - "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, ""); - return; - } - - final ArrayList suggestionsList = - new ArrayList(); - suggestionsList.add(suggestedNet.build()); - - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList( - android.provider.Settings.EXTRA_WIFI_NETWORK_LIST, suggestionsList); - Intent intent = new Intent(android.provider.Settings.ACTION_WIFI_ADD_NETWORKS); - intent.putExtras(bundle); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - moContext.startActivity(intent); - - poResult.success(null); - } else { - // Deprecated version - android.net.wifi.WifiConfiguration conf = - generateConfiguration(ssid, bssid, password, security, isHidden); - - int updateNetwork = registerWifiNetworkDeprecated(conf, false); - - if (updateNetwork == -1) { - poResult.error("Error", "Error updating network configuration", ""); - } else { - poResult.success(null); - } - } - } - - /// Send the ssid and password of a Wifi network into this to connect to the network. - /// Example: wifi.findAndConnect(ssid, password); - /// After 10 seconds, a post telling you whether you are connected will pop up. - /// Callback returns true if ssid is in the range - private void findAndConnect(final MethodCall poCall, final Result poResult) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - if (requestingPermission) { - poResult.error( - "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); - return; - } - requestingPermission = true; - permissionRequestResultCallback = poResult; - permissionRequestCookie.clear(); - permissionRequestCookie.add(poCall); - moActivity.requestPermissions( - new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, - PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT); - // actual call will be handled in [onRequestPermissionsResult] - } else { - _findAndConnect(poCall, poResult); - } - } - - private void _findAndConnect(final MethodCall poCall, final Result poResult) { - new Thread() { - public void run() { - String ssid = poCall.argument("ssid"); - String bssid = poCall.argument("bssid"); - String password = poCall.argument("password"); - Boolean joinOnce = poCall.argument("join_once"); - Boolean withInternet = poCall.argument("with_internet"); - - String security = null; - List results = moWiFi.getScanResults(); - for (ScanResult result : results) { - String resultString = "" + result.SSID; - if (ssid.equals(resultString) - && (result.BSSID == null || bssid == null || result.BSSID.equals(bssid))) { - security = getSecurityType(result); - if (bssid == null) { - bssid = result.BSSID; - } - } - } - - connectTo(poResult, ssid, bssid, password, security, joinOnce, withInternet, false); - } - }.start(); - } - - private static String getSecurityType(ScanResult scanResult) { - String capabilities = scanResult.capabilities; - - if (capabilities.contains("WPA") - || capabilities.contains("WPA2") - || capabilities.contains("WPA/WPA2 PSK")) { - return "WPA"; - } else if (capabilities.contains("WEP")) { - return "WEP"; - } else { - return null; - } - } - - /// Use this method to check if the device is currently connected to Wifi. - private void isConnected(Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - isConnectedDeprecated(poResult); - } else { - if (moContext.checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) - != PackageManager.PERMISSION_GRANTED) { - if (requestingPermission) { - poResult.error( - "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); - return; - } - requestingPermission = true; - permissionRequestResultCallback = poResult; - moActivity.requestPermissions( - new String[] {Manifest.permission.ACCESS_NETWORK_STATE}, - PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED); - // actual call will be handled in [onRequestPermissionsResult] - } else { - _isConnected(poResult); - } - } + } } private void _isConnected(Result poResult) { ConnectivityManager connManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); boolean result = false; if (connManager != null) { // `connManager.getActiveNetwork` only return if the network has internet // therefore using `connManager.getAllNetworks()` to check all networks for (final Network network : connManager.getAllNetworks()) { final NetworkCapabilities capabilities = - network != null ? connManager.getNetworkCapabilities(network) : null; + network != null ? connManager.getNetworkCapabilities(network) : null; final boolean isConnected = - capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); + capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); if (isConnected) { result = true; break; @@ -1103,9 +746,9 @@ private void _isConnected(Result poResult) { @SuppressWarnings("deprecation") private void isConnectedDeprecated(Result poResult) { ConnectivityManager connManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); android.net.NetworkInfo mWifi = - connManager != null ? connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI) : null; + connManager != null ? connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI) : null; poResult.success(mWifi != null && mWifi.isConnected()); } @@ -1123,7 +766,7 @@ private void disconnect(Result poResult) { } else { if (networkCallback != null) { final ConnectivityManager connectivityManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); connectivityManager.unregisterNetworkCallback(networkCallback); networkCallback = null; disconnected = true; @@ -1132,8 +775,8 @@ private void disconnect(Result poResult) { disconnected = networksRemoved == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; } else { Log.e( - WifiIotPlugin.class.getSimpleName(), - "Can't disconnect from WiFi, networkCallback and networkSuggestions is null."); + WifiIotPlugin.class.getSimpleName(), + "Can't disconnect from WiFi, networkCallback and networkSuggestions is null."); } } poResult.success(disconnected); @@ -1200,7 +843,7 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { List mWifiConfigList = moWiFi.getConfiguredNetworks(); for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { String comparableSSID = - ('"' + prefix_ssid + '"'); //Add quotes because wifiConfig.SSID has them + ('"' + prefix_ssid + '"'); //Add quotes because wifiConfig.SSID has them if (wifiConfig.SSID.equals(comparableSSID)) { Boolean isRemoved = moWiFi.removeNetwork(wifiConfig.networkId); if (isRemoved) { @@ -1273,36 +916,36 @@ private static String longToIP(int longIp) { /// Method to connect to WIFI Network private void connectTo( - final Result poResult, - final String ssid, - final String bssid, - final String password, - final String security, - final Boolean joinOnce, - final Boolean withInternet, - final Boolean isHidden) { + final Result poResult, + final String ssid, + final String bssid, + final String password, + final String security, + final Boolean joinOnce, + final Boolean withInternet, + final Boolean isHidden) { final Handler handler = new Handler(Looper.getMainLooper()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { final boolean connected = - connectToDeprecated(ssid, bssid, password, security, joinOnce, isHidden); + connectToDeprecated(ssid, bssid, password, security, joinOnce, isHidden); handler.post( - new Runnable() { - @Override - public void run() { - poResult.success(connected); - } - }); + new Runnable() { + @Override + public void run() { + poResult.success(connected); + } + }); } else { // error if WEP security, since not supported if (security != null && security.toUpperCase().equals("WEP")) { handler.post( - new Runnable() { - @Override - public void run() { - poResult.error( - "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, ""); - } - }); + new Runnable() { + @Override + public void run() { + poResult.error( + "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, ""); + } + }); return; } @@ -1316,12 +959,12 @@ public void run() { final MacAddress macAddress = macAddressFromBssid(bssid); if (macAddress == null) { handler.post( - new Runnable() { - @Override - public void run() { - poResult.error("Error", "Invalid BSSID representation", ""); - } - }); + new Runnable() { + @Override + public void run() { + poResult.error("Error", "Invalid BSSID representation", ""); + } + }); return; } builder.setBssid(macAddress); @@ -1350,12 +993,12 @@ public void run() { Log.e(WifiIotPlugin.class.getSimpleName(), "status: " + status); handler.post( - new Runnable() { - @Override - public void run() { - poResult.success(status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); - } - }); + new Runnable() { + @Override + public void run() { + poResult.success(status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); + } + }); } else { // Make new network specifier final WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder(); @@ -1366,12 +1009,12 @@ public void run() { final MacAddress macAddress = macAddressFromBssid(bssid); if (macAddress == null) { handler.post( - new Runnable() { - @Override - public void run() { - poResult.error("Error", "Invalid BSSID representation", ""); - } - }); + new Runnable() { + @Override + public void run() { + poResult.error("Error", "Invalid BSSID representation", ""); + } + }); return; } builder.setBssid(macAddress); @@ -1383,39 +1026,39 @@ public void run() { } final NetworkRequest networkRequest = - new NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .setNetworkSpecifier(builder.build()) - .build(); + new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .setNetworkSpecifier(builder.build()) + .build(); final ConnectivityManager connectivityManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); if (networkCallback != null) connectivityManager.unregisterNetworkCallback(networkCallback); networkCallback = - new ConnectivityManager.NetworkCallback() { - boolean resultSent = false; + new ConnectivityManager.NetworkCallback() { + boolean resultSent = false; - @Override - public void onAvailable(@NonNull Network network) { - super.onAvailable(network); - if (!resultSent) { - poResult.success(true); - resultSent = true; - } - } + @Override + public void onAvailable(@NonNull Network network) { + super.onAvailable(network); + if (!resultSent) { + poResult.success(true); + resultSent = true; + } + } - @Override - public void onUnavailable() { - super.onUnavailable(); - if (!resultSent) { - poResult.success(false); - resultSent = true; - } - } - }; + @Override + public void onUnavailable() { + super.onUnavailable(); + if (!resultSent) { + poResult.success(false); + resultSent = true; + } + } + }; connectivityManager.requestNetwork(networkRequest, networkCallback, handler, 30 * 1000); } @@ -1424,7 +1067,7 @@ public void onUnavailable() { @SuppressWarnings("deprecation") private int registerWifiNetworkDeprecated( - android.net.wifi.WifiConfiguration conf, Boolean joinOnce) { + android.net.wifi.WifiConfiguration conf, Boolean joinOnce) { int updateNetwork = -1; int registeredNetwork = -1; @@ -1434,7 +1077,7 @@ private int registerWifiNetworkDeprecated( if (mWifiConfigList != null) { for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { if (wifiConfig.SSID.equals(conf.SSID) - && (wifiConfig.BSSID == null + && (wifiConfig.BSSID == null || conf.BSSID == null || wifiConfig.BSSID.equals(conf.BSSID))) { conf.networkId = wifiConfig.networkId; @@ -1462,154 +1105,492 @@ private int registerWifiNetworkDeprecated( int ssidRandomizedExtraLength = ssidRandomized.length() - 32; if (ssidRandomizedExtraLength > 0) { ssidRandomized = - ssid.substring(0, ssid.length() - ssidRandomizedExtraLength) + randomInteger; + ssid.substring(0, ssid.length() - ssidRandomizedExtraLength) + randomInteger; } conf.SSID = "\"" + ssidRandomized + "\""; updateNetwork = moWiFi.addNetwork(conf); // Add my wifi with another name conf.SSID = ssid; conf.networkId = updateNetwork; updateNetwork = - moWiFi.updateNetwork( - conf); // After my wifi is added with another name, I change it to the desired name + moWiFi.updateNetwork( + conf); // After my wifi is added with another name, I change it to the desired name moWiFi.saveConfiguration(); if (updateNetwork != -1) { break; } } } - //no need to continue looping - break; + //no need to continue looping + break; + } + } + } + + /// If network not already in configured networks add new network + if (updateNetwork == -1) { + updateNetwork = moWiFi.addNetwork(conf); + conf.networkId = updateNetwork; + moWiFi.saveConfiguration(); + } + + // Try returning last known valid network id + if (updateNetwork == -1) { + return registeredNetwork; + } + + return updateNetwork; + } + + private android.net.wifi.WifiConfiguration generateConfiguration( + String ssid, String bssid, String password, String security, Boolean isHidden) { + android.net.wifi.WifiConfiguration conf = new android.net.wifi.WifiConfiguration(); + conf.SSID = "\"" + ssid + "\""; + conf.hiddenSSID = isHidden != null ? isHidden : false; + if (bssid != null) { + conf.BSSID = bssid; + } + + if (security != null) security = security.toUpperCase(); + else security = "NONE"; + + if (security.toUpperCase().equals("WPA")) { + + /// appropriate ciper is need to set according to security type used, + /// ifcase of not added it will not be able to connect + conf.preSharedKey = "\"" + password + "\""; + + conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.RSN); + + conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); + + conf.status = android.net.wifi.WifiConfiguration.Status.ENABLED; + + conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.TKIP); + conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.CCMP); + + conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); + + conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.TKIP); + conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.CCMP); + + conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.RSN); + conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.WPA); + } else if (security.equals("WEP")) { + conf.wepKeys[0] = "\"" + password + "\""; + conf.wepTxKeyIndex = 0; + conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.NONE); + conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.WEP40); + } else { + conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.NONE); + } + + return conf; + } + + @SuppressWarnings("deprecation") + private Boolean connectToDeprecated( + String ssid, + String bssid, + String password, + String security, + Boolean joinOnce, + Boolean isHidden) { + /// Make new configuration + android.net.wifi.WifiConfiguration conf = + generateConfiguration(ssid, bssid, password, security, isHidden); + + int updateNetwork = registerWifiNetworkDeprecated(conf, joinOnce); + + if (updateNetwork == -1) { + return false; + } + + if (joinOnce != null && joinOnce.booleanValue()) { + ssidsToBeRemovedOnExit.add(conf.SSID); + } + if (lastConnectedNetworkId == -1) { + lastConnectedNetworkId = moWiFi.getConnectionInfo().getNetworkId(); + } + + boolean disconnect = moWiFi.disconnect(); + if (!disconnect) { + return false; + } + + boolean enabled = moWiFi.enableNetwork(updateNetwork, true); + if (!enabled) return false; + + boolean connected = false; + int networkId = -1; + for (int i = 0; i < 20; i++) { + WifiInfo currentNet = moWiFi.getConnectionInfo(); + networkId = currentNet.getNetworkId(); + SupplicantState netState = currentNet.getSupplicantState(); + + // Wait for connection to reach state completed + // to discard false positives like auth error + if (networkId != -1 && netState == SupplicantState.COMPLETED) { + connected = networkId == updateNetwork; + if (connected) { + break; + } else { + disconnect = moWiFi.disconnect(); + if (!disconnect) { + break; + } + + enabled = moWiFi.enableNetwork(updateNetwork, true); + break; + } + } + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + break; + } + } + if (!connected && lastConnectedNetworkId != -1) { + //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID + moWiFi.enableNetwork(lastConnectedNetworkId, true); + } + return connected; + } + + /** + * The network's SSID. Can either be an ASCII string, which must be enclosed in double quotation + * marks (e.g., {@code "MyNetwork"}), or a string of hex digits, which are not enclosed in quotes + * (e.g., {@code 01a243f405}). + */ + private void getWiFiAPSSID(Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + + if (oWiFiConfig != null && oWiFiConfig.SSID != null) { + poResult.success(oWiFiConfig.SSID); + return; + } + + poResult.error("Exception [getWiFiAPSSID]", "SSID not found", null); + } else { + if (apReservation != null) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); + if (wifiConfiguration != null) { + poResult.success(wifiConfiguration.SSID); + } else { + poResult.error( + "Exception [getWiFiAPSSID]", + "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", + null); + } + } else { + SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); + poResult.success(softApConfiguration.getSsid()); + } + } else { + poResult.error("Exception [getWiFiAPSSID]", "Hotspot is not enabled.", null); + } + } + } + + private void setWiFiAPSSID(MethodCall poCall, Result poResult) { + String sAPSSID = poCall.argument("ssid"); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + + oWiFiConfig.SSID = sAPSSID; + + moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); + + poResult.success(null); + } else { + poResult.error( + "Exception [setWiFiAPSSID]", + "Setting SSID name is not supported on API level >= 26", + null); + } + } + + /** + * This is a network that does not broadcast its SSID, so an SSID-specific probe request must be + * used for scans. + */ + private void isSSIDHidden(Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + + if (oWiFiConfig != null && oWiFiConfig.hiddenSSID) { + poResult.success(oWiFiConfig.hiddenSSID); + return; + } + + poResult.error("Exception [isSSIDHidden]", "Wifi AP not Supported", null); + } else { + if (apReservation != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); + poResult.success(softApConfiguration.isHiddenSsid()); + } else { + WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); + if (wifiConfiguration != null) { + poResult.success(wifiConfiguration.hiddenSSID); + } else { + poResult.error( + "Exception [isSSIDHidden]", + "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", + null); + } } + } else { + poResult.error("Exception [isSSIDHidden]", "Hotspot is not enabled.", null); } } + } - /// If network not already in configured networks add new network - if (updateNetwork == -1) { - updateNetwork = moWiFi.addNetwork(conf); - conf.networkId = updateNetwork; - moWiFi.saveConfiguration(); - } - - // Try returning last known valid network id - if (updateNetwork == -1) { - return registeredNetwork; - } + private void setSSIDHidden(MethodCall poCall, Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + boolean isSSIDHidden = poCall.argument("hidden"); - return updateNetwork; - } + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - private android.net.wifi.WifiConfiguration generateConfiguration( - String ssid, String bssid, String password, String security, Boolean isHidden) { - android.net.wifi.WifiConfiguration conf = new android.net.wifi.WifiConfiguration(); - conf.SSID = "\"" + ssid + "\""; - conf.hiddenSSID = isHidden != null ? isHidden : false; - if (bssid != null) { - conf.BSSID = bssid; - } + oWiFiConfig.hiddenSSID = isSSIDHidden; - if (security != null) security = security.toUpperCase(); - else security = "NONE"; + moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - if (security.toUpperCase().equals("WPA")) { + poResult.success(null); + } else { + poResult.error( + "Exception [setSSIDHidden]", + "Setting SSID visibility is not supported on API level >= 26", + null); + } + } - /// appropriate ciper is need to set according to security type used, - /// ifcase of not added it will not be able to connect - conf.preSharedKey = "\"" + password + "\""; + /** + * Pre-shared key for use with WPA-PSK. Either an ASCII string enclosed in double quotation marks + * (e.g., {@code "abcdefghij"} for PSK passphrase or a string of 64 hex digits for raw PSK. + * + *

When the value of this key is read, the actual key is not returned, just a "*" if the key + * has a value, or the null string otherwise. + */ + private void getWiFiAPPreSharedKey(Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.RSN); + if (oWiFiConfig != null && oWiFiConfig.preSharedKey != null) { + poResult.success(oWiFiConfig.preSharedKey); + return; + } - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); + poResult.error("Exception", "Wifi AP not Supported", null); + } else { + if (apReservation != null) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); + if (wifiConfiguration != null) { + poResult.success(wifiConfiguration.preSharedKey); + } else { + poResult.error( + "Exception [getWiFiAPPreSharedKey]", + "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", + null); + } + } else { + SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); + poResult.success(softApConfiguration.getPassphrase()); + } + } else { + poResult.error("Exception [getWiFiAPPreSharedKey]", "Hotspot is not enabled.", null); + } + } + } - conf.status = android.net.wifi.WifiConfiguration.Status.ENABLED; + private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + String sPreSharedKey = poCall.argument("preSharedKey"); - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.TKIP); - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.CCMP); + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); + oWiFiConfig.preSharedKey = sPreSharedKey; - conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.TKIP); - conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.CCMP); + moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.RSN); - conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.WPA); - } else if (security.equals("WEP")) { - conf.wepKeys[0] = "\"" + password + "\""; - conf.wepTxKeyIndex = 0; - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.NONE); - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.WEP40); + poResult.success(null); } else { - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.NONE); + poResult.error( + "Exception [setWiFiAPPreSharedKey]", + "Setting WiFi password is not supported on API level >= 26", + null); } - - return conf; } - @SuppressWarnings("deprecation") - private Boolean connectToDeprecated( - String ssid, - String bssid, - String password, - String security, - Boolean joinOnce, - Boolean isHidden) { - /// Make new configuration - android.net.wifi.WifiConfiguration conf = - generateConfiguration(ssid, bssid, password, security, isHidden); - - int updateNetwork = registerWifiNetworkDeprecated(conf, joinOnce); + /** + * Gets a list of the clients connected to the Hotspot *** getClientList : param onlyReachables + * {@code false} if the list should contain unreachable (probably disconnected) clients, {@code + * true} otherwise param reachableTimeout Reachable Timout in miliseconds, 300 is default param + * finishListener, Interface called when the scan method finishes + */ + private void getClientList(MethodCall poCall, final Result poResult) { + Boolean onlyReachables = false; + if (poCall.argument("onlyReachables") != null) { + onlyReachables = poCall.argument("onlyReachables"); + } - if (updateNetwork == -1) { - return false; + Integer reachableTimeout = 300; + if (poCall.argument("reachableTimeout") != null) { + reachableTimeout = poCall.argument("reachableTimeout"); } - if (joinOnce != null && joinOnce.booleanValue()) { - ssidsToBeRemovedOnExit.add(conf.SSID); + final Boolean finalOnlyReachables = onlyReachables; + FinishScanListener oFinishScanListener = + new FinishScanListener() { + @Override + public void onFinishScan(final ArrayList clients) { + try { + JSONArray clientArray = new JSONArray(); + + for (ClientScanResult client : clients) { + JSONObject clientObject = new JSONObject(); + + Boolean clientIsReachable = client.isReachable(); + Boolean shouldReturnCurrentClient = true; + if (finalOnlyReachables.booleanValue()) { + if (!clientIsReachable.booleanValue()) { + shouldReturnCurrentClient = Boolean.valueOf(false); + } + } + if (shouldReturnCurrentClient.booleanValue()) { + try { + clientObject.put("IPAddr", client.getIpAddr()); + clientObject.put("HWAddr", client.getHWAddr()); + clientObject.put("Device", client.getDevice()); + clientObject.put("isReachable", client.isReachable()); + } catch (JSONException e) { + poResult.error("Exception", e.getMessage(), null); + } + clientArray.put(clientObject); + } + } + poResult.success(clientArray.toString()); + } catch (Exception e) { + poResult.error("Exception", e.getMessage(), null); + } + } + }; + + if (reachableTimeout != null) { + moWiFiAPManager.getClientList(onlyReachables, reachableTimeout, oFinishScanListener); + } else { + moWiFiAPManager.getClientList(onlyReachables, oFinishScanListener); } - if (lastConnectedNetworkId == -1) { - lastConnectedNetworkId = moWiFi.getConnectionInfo().getNetworkId(); + } + + /** Gets the Wi-Fi enabled state. *** getWifiApState : return {link WIFI_AP_STATE} */ + private void getWiFiAPState(Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + poResult.success(moWiFiAPManager.getWifiApState().ordinal()); + } else { + poResult.success(localOnlyHotspotState); } + } - boolean disconnect = moWiFi.disconnect(); - if (!disconnect) { - return false; + /** + * Return whether Wi-Fi AP is enabled or disabled. *** isWifiApEnabled : return {@code true} if + * Wi-Fi AP is enabled + */ + private void isWiFiAPEnabled(Result poResult) { + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + try { + poResult.success(moWiFiAPManager.isWifiApEnabled()); + } catch (SecurityException e) { + Log.e(WifiIotPlugin.class.getSimpleName(), e.getMessage(), null); + poResult.error("Exception [isWiFiAPEnabled]", e.getMessage(), null); + } + } else { + poResult.success(apReservation != null); } + } - boolean enabled = moWiFi.enableNetwork(updateNetwork, true); - if (!enabled) return false; + /** + * Start AccessPoint mode with the specified configuration. If the radio is already running in AP + * mode, update the new configuration Note that starting in access point mode disables station + * mode operation *** setWifiApEnabled : param wifiConfig SSID, security and channel details as + * part of WifiConfiguration return {@code true} if the operation succeeds, {@code false} + * otherwise + */ + private void setWiFiAPEnabled(MethodCall poCall, final Result poResult) { + boolean enabled = poCall.argument("state"); - boolean connected = false; - int networkId = -1; - for (int i = 0; i < 20; i++) { - WifiInfo currentNet = moWiFi.getConnectionInfo(); - networkId = currentNet.getNetworkId(); - SupplicantState netState = currentNet.getSupplicantState(); + /** Using LocalOnlyHotspotCallback when setting WiFi AP state on API level >= 29 */ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + final boolean result = moWiFiAPManager.setWifiApEnabled(null, enabled); + poResult.success(result); + } else { + if (enabled) { + localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLING; + moWiFi.startLocalOnlyHotspot( + new WifiManager.LocalOnlyHotspotCallback() { + @Override + public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { + super.onStarted(reservation); + apReservation = reservation; + localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLED; + poResult.success(true); + } - // Wait for connection to reach state completed - // to discard false positives like auth error - if (networkId != -1 && netState == SupplicantState.COMPLETED) { - connected = networkId == updateNetwork; - if (connected) { - break; - } else { - disconnect = moWiFi.disconnect(); - if (!disconnect) { - break; - } + @Override + public void onStopped() { + super.onStopped(); + if (apReservation != null) { + apReservation.close(); + } + apReservation = null; + localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; + Log.d(WifiIotPlugin.class.getSimpleName(), "LocalHotspot Stopped."); + } - enabled = moWiFi.enableNetwork(updateNetwork, true); - break; + @Override + public void onFailed(int reason) { + super.onFailed(reason); + if (apReservation != null) { + apReservation.close(); + } + apReservation = null; + localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_FAILED; + Log.d( + WifiIotPlugin.class.getSimpleName(), + "LocalHotspot failed with code: " + String.valueOf(reason)); + poResult.success(false); + } + }, + new Handler()); + } else { + localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLING; + if (apReservation != null) { + apReservation.close(); + apReservation = null; + poResult.success(true); + } else { + Log.e( + WifiIotPlugin.class.getSimpleName(), "Can't disable WiFi AP, apReservation is null."); + poResult.success(false); } + localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; } - try { - Thread.sleep(1000); - } catch (InterruptedException ignored) { - break; - } - } - if (!connected && lastConnectedNetworkId != -1) { - //android 8.1 won't automatically reconnect to the previous network if it shares the same SSID - moWiFi.enableNetwork(lastConnectedNetworkId, true); } - return connected; + } + + /** + * Show write permission settings page to user Depending on Android version and application these + * may be needed to perform certain WiFi configurations that require WRITE_SETTINGS which require + * a double opt-in, not just presence in manifest. *** showWritePermissionSettings : param boolean + * force, if true shows always, if false only if permissions are not already granted + */ + private void showWritePermissionSettings(MethodCall poCall, Result poResult) { + boolean force = poCall.argument("force"); + moWiFiAPManager.showWritePermissionSettings(force); + poResult.success(null); } }