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" 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..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,25 +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; @@ -71,17 +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<>(); + 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 @@ -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,34 +115,26 @@ private void cleanup() { moWiFiAPManager = null; } - /** 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; + 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); } - }); - registrar.addRequestPermissionsResultListener(wifiIotPlugin); + } + } + } + ssidsToBeRemovedOnExit.clear(); } + // ---------- 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); @@ -191,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; @@ -210,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; @@ -222,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; @@ -232,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; @@ -241,6 +229,7 @@ public boolean onRequestPermissionsResult( return false; } + // ---------- Method channel ---------- @Override public void onMethodCall(MethodCall poCall, Result poResult) { switch (poCall.method) { @@ -294,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); @@ -337,706 +326,369 @@ 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; } } - private void setSSIDHidden(MethodCall poCall, Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - boolean isSSIDHidden = poCall.argument("hidden"); - - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - - oWiFiConfig.hiddenSSID = isSSIDHidden; - - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - - poResult.success(null); + /// 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 { - poResult.error( - "Exception [setSSIDHidden]", - "Setting SSID visibility is not supported on API level >= 26", - null); + _loadWifiList(poResult); } } - /** - * 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(); - - if (oWiFiConfig != null && oWiFiConfig.preSharedKey != null) { - poResult.success(oWiFiConfig.preSharedKey); - return; - } - - 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); - } + private void _loadWifiList(final Result poResult) { + try { + moWiFi.startScan(); + poResult.success(handleNetworkScanResult().toString()); + } catch (Exception e) { + poResult.error("Exception", e.getMessage(), null); } } - private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - String sPreSharedKey = poCall.argument("preSharedKey"); + /// 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. - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); + /// 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"); - oWiFiConfig.preSharedKey = sPreSharedKey; + final ConnectivityManager manager = + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); + 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); + } + }); + } + }); - poResult.success(null); - } else { - poResult.error( - "Exception [setWiFiAPPreSharedKey]", - "Setting WiFi password is not supported on API level >= 26", - null); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + success = manager.bindProcessToNetwork(null); + } else { + success = ConnectivityManager.setProcessDefaultNetwork(null); + } + } + } + if (shouldReply) { + poResult.success(success); } } - /** - * 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"); - } - - 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); - } + /// Method to check if wifi is enabled + private void isEnabled(Result poResult) { + poResult.success(moWiFi.isWifiEnabled()); } - /** - * Return whether Wi-Fi AP is enabled or disabled. *** isWifiApEnabled : return {@code true} if - * Wi-Fi AP is enabled - */ - private void isWiFiAPEnabled(Result poResult) { + /// 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) { - try { - poResult.success(moWiFiAPManager.isWifiApEnabled()); - } catch (SecurityException e) { - Log.e(WifiIotPlugin.class.getSimpleName(), e.getMessage(), null); - poResult.error("Exception [isWiFiAPEnabled]", e.getMessage(), null); + 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."); } - } else { - poResult.success(apReservation != null); } + + poResult.success(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"); + 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"); - /** 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); - } + connectTo(poResult, ssid, bssid, password, security, joinOnce, withInternet, isHidden); + } + }.start(); + } - @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."); - } + /// 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; + } - @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 { + return MacAddress.fromString(bssid); + } catch (IllegalArgumentException invalidRepresentation) { + Log.e( + WifiIotPlugin.class.getSimpleName(), + "Mac address parsing failed for bssid: " + bssid, + invalidRepresentation); + return 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 + * 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 showWritePermissionSettings(MethodCall poCall, Result poResult) { - boolean force = poCall.argument("force"); - moWiFiAPManager.showWritePermissionSettings(force); - poResult.success(null); - } + 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"); - /** 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()); + 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 { - poResult.success(localOnlyHotspotState); + // 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); + } } } - @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"); - @Override - public void onCancel(Object o) { - if (receiver != null) { - moContext.unregisterReceiver(receiver); - receiver = null; - } - } + 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; + } + } + } - private BroadcastReceiver createReceiver(final EventChannel.EventSink eventSink) { - return new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - eventSink.success(handleNetworkScanResult().toString()); + connectTo(poResult, ssid, bssid, password, security, joinOnce, withInternet, false); } - }; + }.start(); } - 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); - - 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; + private static String getSecurityType(ScanResult scanResult) { + String capabilities = scanResult.capabilities; if (capabilities.contains("WPA") - || capabilities.contains("WPA2") - || capabilities.contains("WPA/WPA2 PSK")) { + || capabilities.contains("WPA2") + || capabilities.contains("WPA/WPA2 PSK")) { return "WPA"; } else if (capabilities.contains("WEP")) { return "WEP"; @@ -1051,17 +703,17 @@ private void isConnected(Result poResult) { isConnectedDeprecated(poResult); } else { if (moContext.checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) - != PackageManager.PERMISSION_GRANTED) { + != PackageManager.PERMISSION_GRANTED) { if (requestingPermission) { poResult.error( - "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); + "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); + 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); @@ -1071,16 +723,16 @@ private void isConnected(Result 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; @@ -1094,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()); } @@ -1107,10 +759,14 @@ 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 = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); connectivityManager.unregisterNetworkCallback(networkCallback); networkCallback = null; disconnected = true; @@ -1119,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); @@ -1186,14 +842,25 @@ 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 @@ -1249,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; } @@ -1292,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); @@ -1326,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(); @@ -1342,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); @@ -1358,176 +1025,572 @@ public void run() { builder.setWpa2Passphrase(password); } - final NetworkRequest networkRequest = - 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); - - if (networkCallback != null) connectivityManager.unregisterNetworkCallback(networkCallback); + final NetworkRequest networkRequest = + 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); + + if (networkCallback != null) connectivityManager.unregisterNetworkCallback(networkCallback); + + networkCallback = + 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 onUnavailable() { + super.onUnavailable(); + if (!resultSent) { + poResult.success(false); + resultSent = true; + } + } + }; + + connectivityManager.requestNetwork(networkRequest, networkCallback, handler, 30 * 1000); + } + } + } + + @SuppressWarnings("deprecation") + private int registerWifiNetworkDeprecated( + android.net.wifi.WifiConfiguration conf, Boolean joinOnce) { + int updateNetwork = -1; + int registeredNetwork = -1; + + /// Remove the existing configuration for this netwrok + List mWifiConfigList = moWiFi.getConfiguredNetworks(); + + if (mWifiConfigList != null) { + for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { + if (wifiConfig.SSID.equals(conf.SSID) + && (wifiConfig.BSSID == null + || conf.BSSID == null + || wifiConfig.BSSID.equals(conf.BSSID))) { + conf.networkId = wifiConfig.networkId; + 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()) { + 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; + } + } + } + + /// 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); + } + } + } + + private void setSSIDHidden(MethodCall poCall, Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + boolean isSSIDHidden = poCall.argument("hidden"); - networkCallback = - new ConnectivityManager.NetworkCallback() { - boolean resultSent = false; + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - @Override - public void onAvailable(@NonNull Network network) { - super.onAvailable(network); - if (!resultSent) { - poResult.success(true); - resultSent = true; - } - } + oWiFiConfig.hiddenSSID = isSSIDHidden; - @Override - public void onUnavailable() { - super.onUnavailable(); - if (!resultSent) { - poResult.success(false); - resultSent = true; - } - } - }; + moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - connectivityManager.requestNetwork(networkRequest, networkCallback, handler, 30 * 1000); - } + poResult.success(null); + } else { + poResult.error( + "Exception [setSSIDHidden]", + "Setting SSID visibility is not supported on API level >= 26", + null); } } - @SuppressWarnings("deprecation") - private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration conf) { - int updateNetwork = -1; - int registeredNetwork = -1; + /** + * 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(); - /// Remove the existing configuration for this netwrok - List mWifiConfigList = moWiFi.getConfiguredNetworks(); + if (oWiFiConfig != null && oWiFiConfig.preSharedKey != null) { + poResult.success(oWiFiConfig.preSharedKey); + return; + } - if (mWifiConfigList != null) { - for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { - if (wifiConfig.SSID.equals(conf.SSID) - && (wifiConfig.BSSID == null - || conf.BSSID == null - || wifiConfig.BSSID.equals(conf.BSSID))) { - conf.networkId = wifiConfig.networkId; - registeredNetwork = wifiConfig.networkId; - updateNetwork = moWiFi.updateNetwork(conf); + 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); } } - - /// If network not already in configured networks add new network - if (updateNetwork == -1) { - updateNetwork = moWiFi.addNetwork(conf); - 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")) { + private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + String sPreSharedKey = poCall.argument("preSharedKey"); - /// 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 + "\""; + android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.RSN); + oWiFiConfig.preSharedKey = sPreSharedKey; - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); + moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - conf.status = android.net.wifi.WifiConfiguration.Status.ENABLED; + poResult.success(null); + } else { + poResult.error( + "Exception [setWiFiAPPreSharedKey]", + "Setting WiFi password is not supported on API level >= 26", + null); + } + } - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.TKIP); - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.CCMP); + /** + * 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"); + } - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); + Integer reachableTimeout = 300; + if (poCall.argument("reachableTimeout") != null) { + reachableTimeout = poCall.argument("reachableTimeout"); + } - conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.TKIP); - conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.CCMP); + 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); + } + } + }; - 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); + if (reachableTimeout != null) { + moWiFiAPManager.getClientList(onlyReachables, reachableTimeout, oFinishScanListener); } else { - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.NONE); + moWiFiAPManager.getClientList(onlyReachables, oFinishScanListener); } - - 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); - - if (updateNetwork == -1) { - return false; + /** 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); } + } - if (joinOnce != null && joinOnce.booleanValue()) { - ssidsToBeRemovedOnExit.add(conf.SSID); - } + /** + * Return whether Wi-Fi AP is enabled or disabled. *** isWifiApEnabled : return {@code true} if + * Wi-Fi AP is enabled + */ + private void isWiFiAPEnabled(Result poResult) { - boolean disconnect = moWiFi.disconnect(); - if (!disconnect) { - return false; + 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; - for (int i = 0; i < 20; i++) { - WifiInfo currentNet = moWiFi.getConnectionInfo(); - int 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; - break; - } - try { - Thread.sleep(500); - } catch (InterruptedException ignored) { - 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."); + } + + @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; } } + } - 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); } }