diff --git a/CHANGELOG.md b/CHANGELOG.md index 78afcfdd..2502494e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2026-09-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`wifi_iot` - `v0.4.1-dev.0`](#wifi_iot---v041-dev0) + - [`wifi_scan` - `v0.5.1-dev.0`](#wifi_scan---v051-dev0) + +--- + +#### `wifi_iot` - `v0.4.1-dev.0` + + - **FEAT**(wifi_iot): Implement Android modern Wi‑Fi API with Legacy/Modern platform split. + - **FIX**(wifi_iot): Resolve SSID from all Wi‑Fi networks on Android 12+. + - **FIX**(wifi_iot): Fix Android compile errors from shadowed `result` locals. + +#### `wifi_scan` - `v0.5.1-dev.0` + + - **FEAT**(wifi_scan): Implement Android modern Wi‑Fi scan API. + + ## 2026-09-05 ### Changes diff --git a/analysis_options.yaml b/analysis_options.yaml index e6b4cb4f..f002263f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -22,7 +22,6 @@ analyzer: linter: rules: # doc-style related: https://dart.dev/guides/language/effective-dart/documentation - - package_api_docs - public_member_api_docs - comment_references - slash_for_doc_comments diff --git a/packages/wifi_iot/CHANGELOG.md b/packages/wifi_iot/CHANGELOG.md index b5d64a66..fdd75fd6 100644 --- a/packages/wifi_iot/CHANGELOG.md +++ b/packages/wifi_iot/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.4.1-dev.0 + + - **FEAT**(wifi_iot): Implement Android modern Wi‑Fi API with Legacy/Modern platform split. + - **FIX**(wifi_iot): Resolve SSID from all Wi‑Fi networks on Android 12+. + - **FIX**(wifi_iot): Fix Android compile errors from shadowed `result` locals. + ## 0.4.0 > Note: This release has breaking changes. diff --git a/packages/wifi_iot/android/src/main/AndroidManifest.xml b/packages/wifi_iot/android/src/main/AndroidManifest.xml index 6cd7e26d..99d05271 100644 --- a/packages/wifi_iot/android/src/main/AndroidManifest.xml +++ b/packages/wifi_iot/android/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + 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 455839e1..5576f7e9 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 @@ -8,26 +8,26 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.ConnectivityManager; -import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; 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; -import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiNetworkSuggestion; +import android.net.wifi.WifiSsid; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.util.Log; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; +import androidx.annotation.Nullable; +import com.alternadom.wifiiot.wifi.WifiConnectCallback; +import com.alternadom.wifiiot.wifi.WifiConnectRequest; +import com.alternadom.wifiiot.wifi.WifiPlatform; import info.whitebyte.hotspotmanager.ClientScanResult; import info.whitebyte.hotspotmanager.FinishScanListener; import info.whitebyte.hotspotmanager.WIFI_AP_STATE; @@ -47,19 +47,18 @@ import org.json.JSONException; import org.json.JSONObject; -/** WifiIotPlugin */ +/** WifiIotPlugin — Flutter binding; STA logic in {@link WifiPlatform} (legacy / modern). */ +@SuppressWarnings("deprecation") // API < 29 SoftAp / WifiConfiguration public class WifiIotPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.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; - private Network joinedNetwork; private WifiManager moWiFi; private Context moContext; private WifiApManager moWiFiAPManager; @@ -67,12 +66,9 @@ public class WifiIotPlugin private BroadcastReceiver receiver; private WifiManager.LocalOnlyHotspotReservation apReservation; 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<>(); - // Permission request management + private WifiPlatform wifiPlatform; + private boolean requestingPermission = false; private Result permissionRequestResultCallback = null; private ArrayList permissionRequestCookie = new ArrayList<>(); @@ -82,88 +78,103 @@ public class WifiIotPlugin 65655437; private static final int PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED = 65655438; - // initialize members of this class with Context + /** API ≤ 32: FINE_LOCATION; API ≥ 33: NEARBY_WIFI_DEVICES (SSID required). */ + private String[] wifiScanPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return new String[] {Manifest.permission.NEARBY_WIFI_DEVICES}; + } + return new String[] {Manifest.permission.ACCESS_FINE_LOCATION}; + } + + private boolean hasWifiScanPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return moContext.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) + == PackageManager.PERMISSION_GRANTED; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED; + } + return true; + } + + private String wifiScanPermissionDeniedMessage() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return "NEARBY_WIFI_DEVICES permission denied"; + } + return "Fine location permission denied"; + } + private void initWithContext(Context context) { moContext = context; moWiFi = (WifiManager) moContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE); moWiFiAPManager = new WifiApManager(moContext.getApplicationContext()); + ConnectivityManager connectivityManager = + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + wifiPlatform = WifiPlatform.create(moContext, moWiFi, connectivityManager); } - // initialize members of this class with Activity private void initWithActivity(Activity activity) { moActivity = 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); - } - } - } + unregisterScanReceiver(); + if (apReservation != null) { + apReservation.close(); + apReservation = null; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !suggestionsToBeRemovedOnExit.isEmpty()) { - moWiFi.removeNetworkSuggestions(suggestionsToBeRemovedOnExit); + if (wifiPlatform != null) { + wifiPlatform.close(); } - // setting all members to null to avoid memory leaks + permissionRequestResultCallback = null; + permissionRequestCookie.clear(); + requestingPermission = false; channel = null; eventChannel = null; moActivity = null; moContext = null; moWiFi = null; moWiFiAPManager = null; + wifiPlatform = null; } @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"); channel.setMethodCallHandler(this); eventChannel.setStreamHandler(this); - - // initializeWithContext initWithContext(binding.getApplicationContext()); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - // set null as channel handlers channel.setMethodCallHandler(null); eventChannel.setStreamHandler(null); - - // set member to null cleanup(); } @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - // init with activity initWithActivity(binding.getActivity()); binding.addRequestPermissionsResultListener(this); } @Override public void onDetachedFromActivityForConfigChanges() { - // set activity to null moActivity = null; } @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - // init with activity initWithActivity(binding.getActivity()); binding.addRequestPermissionsResultListener(this); } @Override public void onDetachedFromActivity() { - // set activity to null moActivity = null; } @@ -178,7 +189,7 @@ public boolean onRequestPermissionsResult( _loadWifiList(permissionRequestResultCallback); } else { permissionRequestResultCallback.error( - "WifiIotPlugin.Permission", "Fine location permission denied", null); + "WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); } requestingPermission = false; return true; @@ -198,7 +209,7 @@ public boolean onRequestPermissionsResult( _findAndConnect(poCall, permissionRequestResultCallback); } else { permissionRequestResultCallback.error( - "WifiIotPlugin.Permission", "Fine location permission denied", null); + "WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); } requestingPermission = false; return true; @@ -218,124 +229,118 @@ public boolean onRequestPermissionsResult( } @Override - public void onMethodCall(MethodCall poCall, Result poResult) { + public void onMethodCall(MethodCall poCall, Result result) { switch (poCall.method) { case "loadWifiList": - loadWifiList(poResult); + loadWifiList(result); break; case "forceWifiUsage": - forceWifiUsage(poCall, poResult); + forceWifiUsage(poCall, result); break; case "isEnabled": - isEnabled(poResult); + isEnabled(result); break; case "setEnabled": - setEnabled(poCall, poResult); + setEnabled(poCall, result); break; case "connect": - connect(poCall, poResult); + connect(poCall, result); break; case "registerWifiNetwork": - registerWifiNetwork(poCall, poResult); + registerWifiNetwork(poCall, result); break; case "findAndConnect": - findAndConnect(poCall, poResult); + findAndConnect(poCall, result); break; case "isConnected": - isConnected(poResult); + isConnected(result); break; case "disconnect": - disconnect(poResult); + disconnect(result); break; case "getSSID": - getSSID(poResult); + getSSID(result); break; case "getBSSID": - getBSSID(poResult); + getBSSID(result); break; case "getCurrentSignalStrength": - getCurrentSignalStrength(poResult); + getCurrentSignalStrength(result); break; case "getFrequency": - getFrequency(poResult); + getFrequency(result); break; case "getIP": - getIP(poResult); + getIP(result); break; case "removeWifiNetwork": - removeWifiNetwork(poCall, poResult); + removeWifiNetwork(poCall, result); break; case "isRegisteredWifiNetwork": - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) - isRegisteredWifiNetwork(poCall, poResult); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) isRegisteredWifiNetwork(poCall, result); else - poResult.error( + result.error( "Error", "isRegisteredWifiNetwork not supported for Android SDK " + Build.VERSION.SDK_INT, null); break; case "isWiFiAPEnabled": - isWiFiAPEnabled(poResult); + isWiFiAPEnabled(result); break; case "setWiFiAPEnabled": - setWiFiAPEnabled(poCall, poResult); + setWiFiAPEnabled(poCall, result); break; case "getWiFiAPState": - getWiFiAPState(poResult); + getWiFiAPState(result); break; case "getClientList": - getClientList(poCall, poResult); + getClientList(poCall, result); break; case "getWiFiAPSSID": - getWiFiAPSSID(poResult); + getWiFiAPSSID(result); break; case "setWiFiAPSSID": - setWiFiAPSSID(poCall, poResult); + setWiFiAPSSID(poCall, result); break; case "isSSIDHidden": - isSSIDHidden(poResult); + isSSIDHidden(result); break; case "setSSIDHidden": - setSSIDHidden(poCall, poResult); + setSSIDHidden(poCall, result); break; case "getWiFiAPPreSharedKey": - getWiFiAPPreSharedKey(poResult); + getWiFiAPPreSharedKey(result); break; case "setWiFiAPPreSharedKey": - setWiFiAPPreSharedKey(poCall, poResult); + setWiFiAPPreSharedKey(poCall, result); break; case "showWritePermissionSettings": - showWritePermissionSettings(poCall, poResult); + showWritePermissionSettings(poCall, result); break; default: - poResult.notImplemented(); + result.notImplemented(); break; } } - /** - * 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) { + private void getWiFiAPSSID(Result result) { 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); + result.success(oWiFiConfig.SSID); return; } - poResult.error("Exception [getWiFiAPSSID]", "SSID not found", null); + result.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); + result.success(wifiConfiguration.SSID); } else { - poResult.error( + result.error( "Exception [getWiFiAPSSID]", "Security type is not WifiConfiguration.KeyMgmt.None or" + " WifiConfiguration.KeyMgmt.WPA2_PSK", @@ -343,58 +348,51 @@ private void getWiFiAPSSID(Result poResult) { } } else { SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.getSsid()); + result.success(softApConfiguration.getSsid()); } } else { - poResult.error("Exception [getWiFiAPSSID]", "Hotspot is not enabled.", null); + result.error("Exception [getWiFiAPSSID]", "Hotspot is not enabled.", null); } } } - private void setWiFiAPSSID(MethodCall poCall, Result poResult) { + private void setWiFiAPSSID(MethodCall poCall, Result result) { 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); + result.success(null); } else { - poResult.error( + result.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) { + private void isSSIDHidden(Result result) { 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); + result.success(oWiFiConfig.hiddenSSID); return; } - poResult.error("Exception [isSSIDHidden]", "Wifi AP not Supported", null); + result.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()); + result.success(softApConfiguration.isHiddenSsid()); } else { WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); if (wifiConfiguration != null) { - poResult.success(wifiConfiguration.hiddenSSID); + result.success(wifiConfiguration.hiddenSSID); } else { - poResult.error( + result.error( "Exception [isSSIDHidden]", "Security type is not WifiConfiguration.KeyMgmt.None or" + " WifiConfiguration.KeyMgmt.WPA2_PSK", @@ -402,55 +400,44 @@ private void isSSIDHidden(Result poResult) { } } } else { - poResult.error("Exception [isSSIDHidden]", "Hotspot is not enabled.", null); + result.error("Exception [isSSIDHidden]", "Hotspot is not enabled.", null); } } } - private void setSSIDHidden(MethodCall poCall, Result poResult) { + private void setSSIDHidden(MethodCall poCall, Result result) { 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); + result.success(null); } else { - poResult.error( + result.error( "Exception [setSSIDHidden]", "Setting SSID visibility is not supported on API level >= 26", null); } } - /** - * 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) { + private void getWiFiAPPreSharedKey(Result result) { 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); + result.success(oWiFiConfig.preSharedKey); return; } - poResult.error("Exception", "Wifi AP not Supported", null); + result.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); + result.success(wifiConfiguration.preSharedKey); } else { - poResult.error( + result.error( "Exception [getWiFiAPPreSharedKey]", "Security type is not WifiConfiguration.KeyMgmt.None or" + " WifiConfiguration.KeyMgmt.WPA2_PSK", @@ -458,40 +445,30 @@ private void getWiFiAPPreSharedKey(Result poResult) { } } else { SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.getPassphrase()); + result.success(softApConfiguration.getPassphrase()); } } else { - poResult.error("Exception [getWiFiAPPreSharedKey]", "Hotspot is not enabled.", null); + result.error("Exception [getWiFiAPPreSharedKey]", "Hotspot is not enabled.", null); } } } - private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { + private void setWiFiAPPreSharedKey(MethodCall poCall, Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { String sPreSharedKey = poCall.argument("preSharedKey"); - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - oWiFiConfig.preSharedKey = sPreSharedKey; - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - - poResult.success(null); + result.success(null); } else { - poResult.error( + result.error( "Exception [setWiFiAPPreSharedKey]", "Setting WiFi password is not supported on API level >= 26", null); } } - /** - * 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) { + private void getClientList(MethodCall poCall, final Result result) { Boolean onlyReachables = false; if (poCall.argument("onlyReachables") != null) { onlyReachables = poCall.argument("onlyReachables"); @@ -527,14 +504,14 @@ public void onFinishScan(final ArrayList clients) { clientObject.put("Device", client.getDevice()); clientObject.put("isReachable", client.isReachable()); } catch (JSONException e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } clientArray.put(clientObject); } } - poResult.success(clientArray.toString()); + result.success(clientArray.toString()); } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } } }; @@ -546,38 +523,25 @@ public void onFinishScan(final ArrayList clients) { } } - /** - * Return whether Wi-Fi AP is enabled or disabled. *** isWifiApEnabled : return {@code true} if - * Wi-Fi AP is enabled - */ - private void isWiFiAPEnabled(Result poResult) { - + private void isWiFiAPEnabled(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { try { - poResult.success(moWiFiAPManager.isWifiApEnabled()); + result.success(moWiFiAPManager.isWifiApEnabled()); } catch (SecurityException e) { Log.e(WifiIotPlugin.class.getSimpleName(), e.getMessage(), null); - poResult.error("Exception [isWiFiAPEnabled]", e.getMessage(), null); + result.error("Exception [isWiFiAPEnabled]", e.getMessage(), null); } } else { - poResult.success(apReservation != null); + result.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) { + private void setWiFiAPEnabled(MethodCall poCall, final Result result) { boolean enabled = poCall.argument("state"); - /** 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 boolean success = moWiFiAPManager.setWifiApEnabled(null, enabled); + result.success(success); } else { if (enabled) { localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLING; @@ -588,7 +552,7 @@ public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { super.onStarted(reservation); apReservation = reservation; localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLED; - poResult.success(true); + result.success(true); } @Override @@ -613,7 +577,7 @@ public void onFailed(int reason) { Log.d( WifiIotPlugin.class.getSimpleName(), "LocalHotspot failed with code: " + String.valueOf(reason)); - poResult.success(false); + result.success(false); } }, new Handler()); @@ -622,43 +586,34 @@ public void onFailed(int reason) { if (apReservation != null) { apReservation.close(); apReservation = null; - poResult.success(true); + result.success(true); } else { Log.e( WifiIotPlugin.class.getSimpleName(), "Can't disable WiFi AP, apReservation is null."); - poResult.success(false); + result.success(false); } localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; } } } - /** - * 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) { + private void showWritePermissionSettings(MethodCall poCall, Result result) { boolean force = poCall.argument("force"); moWiFiAPManager.showWritePermissionSettings(force); - poResult.success(null); + result.success(null); } - /** Gets the Wi-Fi enabled state. *** getWifiApState : return {link WIFI_AP_STATE} */ - private void getWiFiAPState(Result poResult) { + private void getWiFiAPState(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - poResult.success(moWiFiAPManager.getWifiApState().ordinal()); + result.success(moWiFiAPManager.getWifiApState().ordinal()); } else { - poResult.success(localOnlyHotspotState); + result.success(localOnlyHotspotState); } } @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 (!hasWifiScanPermission()) { if (requestingPermission) { return; } @@ -666,26 +621,44 @@ public void onListen(Object o, EventChannel.EventSink eventSink) { 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] + wifiScanPermissions(), PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN); } else { _onListen(eventSink); } } private void _onListen(EventChannel.EventSink eventSink) { + unregisterScanReceiver(); receiver = createReceiver(eventSink); - moContext.registerReceiver( - receiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + registerScanResultsReceiver(receiver, filter); } - @Override - public void onCancel(Object o) { - if (receiver != null) { + @SuppressWarnings("deprecation") // API < 33 + private void registerScanResultsReceiver( + BroadcastReceiver broadcastReceiver, IntentFilter filter) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // API 33+ + moContext.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + } else { + moContext.registerReceiver(broadcastReceiver, filter); + } + } + + private void unregisterScanReceiver() { + if (receiver == null || moContext == null) { + return; + } + try { moContext.unregisterReceiver(receiver); - receiver = null; + } catch (IllegalArgumentException ignored) { + // already unregistered } + receiver = null; + } + + @Override + public void onCancel(Object o) { + unregisterScanReceiver(); } private BroadcastReceiver createReceiver(final EventChannel.EventSink eventSink) { @@ -704,9 +677,9 @@ JSONArray handleNetworkScanResult() { try { for (ScanResult result : results) { JSONObject wifiObject = new JSONObject(); - if (!result.SSID.equals("")) { - - wifiObject.put("SSID", result.SSID); + String ssid = ssidFromScanResult(result); + if (ssid != null && !ssid.isEmpty()) { + wifiObject.put("SSID", ssid); wifiObject.put("BSSID", result.BSSID); wifiObject.put("capabilities", result.capabilities); wifiObject.put("frequency", result.frequency); @@ -716,13 +689,6 @@ JSONArray handleNetworkScanResult() { } 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); } } @@ -733,36 +699,32 @@ JSONArray handleNetworkScanResult() { } } - /// 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) { + private void loadWifiList(final Result result) { + if (!hasWifiScanPermission()) { if (requestingPermission) { - poResult.error( + result.error( "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); return; } requestingPermission = true; - permissionRequestResultCallback = poResult; + permissionRequestResultCallback = result; 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] + wifiScanPermissions(), PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST); } else { - _loadWifiList(poResult); + _loadWifiList(result); } } - private void _loadWifiList(final Result poResult) { + private void _loadWifiList(final Result result) { try { moWiFi.startScan(); - poResult.success(handleNetworkScanResult().toString()); + result.success(handleNetworkScanResult().toString()); } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } } + @SuppressWarnings("deprecation") // API < 23 private boolean selectNetwork(final Network network, final ConnectivityManager manager) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return manager.bindProcessToNetwork(network); @@ -772,27 +734,19 @@ private boolean selectNetwork(final Network network, final ConnectivityManager m } private void onAvailableNetwork( - final ConnectivityManager manager, final Network network, final Result poResult) { - final boolean result = selectNetwork(network, manager); + final ConnectivityManager manager, final Network network, final Result result) { + final boolean success = selectNetwork(network, manager); final Handler handler = new Handler(Looper.getMainLooper()); handler.post( new Runnable() { @Override public void run() { - poResult.success(result); + result.success(success); } }); } - /// 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) { + private void forceWifiUsage(final MethodCall poCall, final Result result) { boolean useWifi = poCall.argument("useWifi"); final ConnectivityManager manager = @@ -802,15 +756,11 @@ private void forceWifiUsage(final MethodCall poCall, final Result poResult) { boolean shouldReply = true; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP && manager != null) { if (useWifi) { - // SDK-31 If not previously in a disconnected state, select the joinedNetwork to ensure - // the correct network is used for communications, else fallback to network manager network. - // https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections + Network joinedNetwork = wifiPlatform.getJoinedNetwork(); if (joinedNetwork != null) { success = selectNetwork(joinedNetwork, manager); } else { - NetworkRequest.Builder builder; - builder = new NetworkRequest.Builder(); - /// set the transport type do WIFI + NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); shouldReply = false; manager.requestNetwork( @@ -820,7 +770,7 @@ private void forceWifiUsage(final MethodCall poCall, final Result poResult) { public void onAvailable(Network network) { super.onAvailable(network); manager.unregisterNetworkCallback(this); - onAvailableNetwork(manager, network, poResult); + onAvailableNetwork(manager, network, result); } }); } @@ -829,26 +779,22 @@ public void onAvailable(Network network) { } } if (shouldReply) { - poResult.success(success); + result.success(success); } } - /// Method to check if wifi is enabled - private void isEnabled(Result poResult) { - poResult.success(moWiFi.isWifiEnabled()); + private void isEnabled(Result result) { + result.success(moWiFi.isWifiEnabled()); } - /// Method to connect/disconnect wifi service - private void setEnabled(MethodCall poCall, Result poResult) { + @SuppressWarnings("deprecation") // API < 29 + private void setEnabled(MethodCall poCall, Result result) { 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 { + } else { if (shouldOpenSettings != null) { if (shouldOpenSettings) { Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS); @@ -863,145 +809,91 @@ private void setEnabled(MethodCall poCall, Result poResult) { } } - poResult.success(null); + result.success(null); } - private void connect(final MethodCall poCall, final Result poResult) { + private void connect(final MethodCall poCall, final Result result) { 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"); - Integer timeoutInSeconds = poCall.argument("timeout_in_seconds"); - - connectTo( - poResult, - ssid, - bssid, - password, - security, - joinOnce, - withInternet, - isHidden, - timeoutInSeconds); + WifiConnectRequest request = + new WifiConnectRequest( + poCall.argument("ssid"), + poCall.argument("bssid"), + poCall.argument("password"), + poCall.argument("security"), + poCall.argument("join_once"), + poCall.argument("with_internet"), + poCall.argument("is_hidden"), + poCall.argument("timeout_in_seconds")); + connectWithResult(request, result); } }.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; - } + private void connectWithResult(WifiConnectRequest request, final Result result) { + final Handler handler = new Handler(Looper.getMainLooper()); + wifiPlatform.connect( + request, + new WifiConnectCallback() { + @Override + public void onSuccess(final boolean connected) { + if (Looper.myLooper() == Looper.getMainLooper()) { + result.success(connected); + } else { + handler.post(() -> result.success(connected)); + } + } - try { - return MacAddress.fromString(bssid); - } catch (IllegalArgumentException invalidRepresentation) { - Log.e( - WifiIotPlugin.class.getSimpleName(), - "Mac address parsing failed for bssid: " + bssid, - invalidRepresentation); - return null; - } + @Override + public void onError(final String code, final String message, final Object details) { + if (Looper.myLooper() == Looper.getMainLooper()) { + result.error(code, message, details); + } else { + handler.post(() -> result.error(code, message, details)); + } + } + }); } - /** - * 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); + private void registerWifiNetwork(final MethodCall poCall, final Result result) { + wifiPlatform.registerNetwork( + poCall.argument("ssid"), + poCall.argument("bssid"), + poCall.argument("password"), + poCall.argument("security"), + poCall.argument("is_hidden"), + new WifiPlatform.RegisterCallback() { + @Override + public void onSuccess() { + result.success(null); + } - if (updateNetwork == -1) { - poResult.error("Error", "Error updating network configuration", ""); - } else { - poResult.success(null); - } - } + @Override + public void onError(String code, String message, Object details) { + result.error(code, message, details); + } + }); } - /// 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) { + private void findAndConnect(final MethodCall poCall, final Result result) { + if (!hasWifiScanPermission()) { if (requestingPermission) { - poResult.error( + result.error( "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); return; } requestingPermission = true; - permissionRequestResultCallback = poResult; + permissionRequestResultCallback = result; 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] + wifiScanPermissions(), PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT); } else { - _findAndConnect(poCall, poResult); + _findAndConnect(poCall, result); } } - private void _findAndConnect(final MethodCall poCall, final Result poResult) { + private void _findAndConnect(final MethodCall poCall, final Result result) { new Thread() { public void run() { String ssid = poCall.argument("ssid"); @@ -1014,187 +906,131 @@ public void run() { String security = null; List results = moWiFi.getScanResults(); for (ScanResult result : results) { - String resultString = "" + result.SSID; + String resultString = ssidFromScanResult(result); if (ssid.equals(resultString) && (result.BSSID == null || bssid == null || result.BSSID.equals(bssid))) { - security = getSecurityType(result); + security = securityTypeFromScanResult(result); if (bssid == null) { bssid = result.BSSID; } } } - connectTo( - poResult, - ssid, - bssid, - password, - security, - joinOnce, - withInternet, - false, - timeoutInSeconds); + WifiConnectRequest request = + new WifiConnectRequest( + ssid, bssid, password, security, joinOnce, withInternet, false, timeoutInSeconds); + connectWithResult(request, result); } }.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) { + private void isConnected(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - isConnectedDeprecated(poResult); + isConnectedDeprecated(result); } else { if (moContext.checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED) { if (requestingPermission) { - poResult.error( + result.error( "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); return; } requestingPermission = true; - permissionRequestResultCallback = poResult; + permissionRequestResultCallback = result; 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); + _isConnected(result); } } } - private void _isConnected(Result poResult) { + private void _isConnected(Result result) { ConnectivityManager connManager = (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - boolean result = false; + boolean isConnected = 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; - final boolean isConnected = + final boolean hasWifi = capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); - if (isConnected) { - result = true; + if (hasWifi) { + isConnected = true; break; } } } - poResult.success(result); + result.success(isConnected); } - @SuppressWarnings("deprecation") - private void isConnectedDeprecated(Result poResult) { + @SuppressWarnings("deprecation") // API < 23 + private void isConnectedDeprecated(Result result) { ConnectivityManager connManager = (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); android.net.NetworkInfo mWifi = connManager != null ? connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI) : null; - poResult.success(mWifi != null && mWifi.isConnected()); + result.success(mWifi != null && mWifi.isConnected()); } - /// Disconnect current Wifi. - private void disconnect(Result poResult) { - boolean disconnected = false; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - //noinspection deprecation - disconnected = moWiFi.disconnect(); - } else { - if (networkCallback != null) { - final ConnectivityManager connectivityManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - connectivityManager.unregisterNetworkCallback(networkCallback); - networkCallback = null; - disconnected = true; - joinedNetwork = null; - } else if (networkSuggestions != null) { - final int networksRemoved = moWiFi.removeNetworkSuggestions(networkSuggestions); - disconnected = networksRemoved == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; - } else { - Log.e( - WifiIotPlugin.class.getSimpleName(), - "Can't disconnect from WiFi, networkCallback and networkSuggestions is null."); - } - } - poResult.success(disconnected); + private void disconnect(Result result) { + result.success(wifiPlatform.disconnect()); } - /// This method will return current ssid - private void getSSID(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); - - // This value should be wrapped in double quotes, so we need to unwrap it. - String ssid = info.getSSID(); - if (ssid.startsWith("\"") && ssid.endsWith("\"")) { - ssid = ssid.substring(1, ssid.length() - 1); + private void getSSID(Result result) { + if (!hasWifiScanPermission()) { + result.error("WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); + return; } - - poResult.success(ssid); + result.success(wifiPlatform.getSsid()); } - /// This method will return the basic service set identifier (BSSID) of the current access point - private void getBSSID(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); - + private void getBSSID(Result result) { + if (!hasWifiScanPermission()) { + result.error("WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); + return; + } + WifiInfo info = wifiPlatform.getWifiInfo(); String bssid = info.getBSSID(); - try { - poResult.success(bssid.toUpperCase()); + result.success(bssid != null ? bssid.toUpperCase() : null); } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } } - /// This method will return current WiFi signal strength - private void getCurrentSignalStrength(Result poResult) { - int linkSpeed = moWiFi.getConnectionInfo().getRssi(); - poResult.success(linkSpeed); + private void getCurrentSignalStrength(Result result) { + result.success(wifiPlatform.getWifiInfo().getRssi()); } - /// This method will return current WiFi frequency - private void getFrequency(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); + private void getFrequency(Result result) { + WifiInfo info = wifiPlatform.getWifiInfo(); int frequency = 0; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { frequency = info.getFrequency(); } - poResult.success(frequency); + result.success(frequency); } - /// This method will return current IP - private void getIP(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); - String stringip = longToIP(info.getIpAddress()); - poResult.success(stringip); + private void getIP(Result result) { + result.success(wifiPlatform.getIpv4()); } - /// This method will remove the WiFi network as per the passed SSID from the device list - private void removeWifiNetwork(MethodCall poCall, Result poResult) { + @SuppressWarnings("deprecation") // API < 29 + private void removeWifiNetwork(MethodCall poCall, Result result) { String prefix_ssid = poCall.argument("ssid"); if (prefix_ssid.equals("")) { - poResult.error("Error", "No prefix SSID was given!", null); + result.error("Error", "No prefix SSID was given!", null); } boolean removed = false; 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 + List mWifiConfigList = moWiFi.getConfiguredNetworks(); + for (WifiConfiguration wifiConfig : mWifiConfigList) { + String comparableSSID = ('"' + prefix_ssid); if (wifiConfig.SSID.startsWith(comparableSSID)) { moWiFi.removeNetwork(wifiConfig.networkId); moWiFi.saveConfiguration(); @@ -1204,10 +1040,9 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { } } - // remove network suggestion if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { List suggestions = moWiFi.getNetworkSuggestions(); - List removeSuggestions = new ArrayList(); + List removeSuggestions = new ArrayList<>(); for (int i = 0, suggestionsSize = suggestions.size(); i < suggestionsSize; i++) { WifiNetworkSuggestion suggestion = suggestions.get(i); if (suggestion.getSsid().startsWith(prefix_ssid)) { @@ -1217,339 +1052,53 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { final int networksRemoved = moWiFi.removeNetworkSuggestions(removeSuggestions); removed = networksRemoved == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; } - poResult.success(removed); + result.success(removed); } - /// This method will remove the WiFi network as per the passed SSID from the device list - private void isRegisteredWifiNetwork(MethodCall poCall, Result poResult) { - + @SuppressWarnings("deprecation") // API < 29 + private void isRegisteredWifiNetwork(MethodCall poCall, Result result) { String ssid = poCall.argument("ssid"); - - List mWifiConfigList = moWiFi.getConfiguredNetworks(); - String comparableSSID = ('"' + ssid + '"'); // Add quotes because wifiConfig.SSID has them + List mWifiConfigList = moWiFi.getConfiguredNetworks(); + String comparableSSID = ('"' + ssid + '"'); if (mWifiConfigList != null) { - for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { + for (WifiConfiguration wifiConfig : mWifiConfigList) { if (wifiConfig.SSID.equals(comparableSSID)) { - poResult.success(true); + result.success(true); return; } } } - poResult.success(false); - } - - private static String longToIP(int longIp) { - StringBuilder sb = new StringBuilder(""); - String[] strip = new String[4]; - strip[3] = String.valueOf((longIp >>> 24)); - strip[2] = String.valueOf((longIp & 0x00FFFFFF) >>> 16); - strip[1] = String.valueOf((longIp & 0x0000FFFF) >>> 8); - strip[0] = String.valueOf((longIp & 0x000000FF)); - sb.append(strip[0]); - sb.append("."); - sb.append(strip[1]); - sb.append("."); - sb.append(strip[2]); - sb.append("."); - sb.append(strip[3]); - return sb.toString(); + result.success(false); } - /// 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 Integer timeoutInSeconds) { - 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); - handler.post( - 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, ""); - } - }); - return; - } - - if (withInternet != null && withInternet) { - // create network suggestion - final WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder(); - // set ssid - builder.setSsid(ssid); - builder.setIsHiddenSsid(isHidden != null ? isHidden : false); - if (bssid != null) { - final MacAddress macAddress = macAddressFromBssid(bssid); - if (macAddress == null) { - handler.post( - new Runnable() { - @Override - public void run() { - poResult.error("Error", "Invalid BSSID representation", ""); - } - }); - return; - } - builder.setBssid(macAddress); - } - - // set password - if (security != null && security.toUpperCase().equals("WPA")) { - builder.setWpa2Passphrase(password); - } - - // remove suggestions if already existing - if (networkSuggestions != null) { - moWiFi.removeNetworkSuggestions(networkSuggestions); - } - - // builder.setIsAppInteractionRequired(true); - final WifiNetworkSuggestion suggestion = builder.build(); - - networkSuggestions = new ArrayList<>(); - networkSuggestions.add(suggestion); - if (joinOnce != null && joinOnce) { - suggestionsToBeRemovedOnExit.add(suggestion); - } - - final int status = moWiFi.addNetworkSuggestions(networkSuggestions); - Log.e(WifiIotPlugin.class.getSimpleName(), "status: " + status); - - handler.post( - 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(); - // set ssid - builder.setSsid(ssid); - builder.setIsHiddenSsid(isHidden != null ? isHidden : false); - if (bssid != null) { - final MacAddress macAddress = macAddressFromBssid(bssid); - if (macAddress == null) { - handler.post( - new Runnable() { - @Override - public void run() { - poResult.error("Error", "Invalid BSSID representation", ""); - } - }); - return; - } - builder.setBssid(macAddress); - } - - // set security - if (security != null && security.toUpperCase().equals("WPA")) { - 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); - - networkCallback = - new ConnectivityManager.NetworkCallback() { - boolean resultSent = false; - - @Override - public void onAvailable(@NonNull Network network) { - super.onAvailable(network); - if (!resultSent) { - joinedNetwork = network; - poResult.success(true); - resultSent = true; - } - } - - @Override - public void onUnavailable() { - super.onUnavailable(); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - connectivityManager.unregisterNetworkCallback(this); - } - if (!resultSent) { - poResult.success(false); - resultSent = true; - } - } - - @Override - public void onLost(Network network) { - super.onLost(network); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - connectivityManager.unregisterNetworkCallback(this); - } - } - }; - - connectivityManager.requestNetwork( - networkRequest, networkCallback, handler, timeoutInSeconds * 1000); + /** API 33+: WifiSsid; API < 33: SSID. */ + @Nullable + @SuppressWarnings("deprecation") // API < 33 + private static String ssidFromScanResult(ScanResult result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + WifiSsid wifiSsid = result.getWifiSsid(); + if (wifiSsid == null) { + return null; } - } - } - - @SuppressWarnings("deprecation") - private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration conf) { - 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; - updateNetwork = moWiFi.updateNetwork(conf); - } + String ssid = wifiSsid.toString(); + if (ssid.startsWith("\"") && ssid.endsWith("\"") && ssid.length() >= 2) { + return ssid.substring(1, ssid.length() - 1); } + return ssid; } - - /// 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; + return result.SSID; } - 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); - - if (updateNetwork == -1) { - return false; - } - - if (joinOnce != null && joinOnce.booleanValue()) { - ssidsToBeRemovedOnExit.add(conf.SSID); - } - - boolean disconnect = moWiFi.disconnect(); - if (!disconnect) { - return false; - } - - boolean enabled = moWiFi.enableNetwork(updateNetwork, true); - if (!enabled) return false; - - boolean connected = false; - for (int i = 0; i < 20; i++) { - WifiInfo currentNet = moWiFi.getConnectionInfo(); - int 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; - } - try { - Thread.sleep(500); - } catch (InterruptedException ignored) { - break; - } + @Nullable + private static String securityTypeFromScanResult(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"; } - - return connected; + return null; } } diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/LegacyWifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/LegacyWifiPlatform.java new file mode 100644 index 00000000..32e6e551 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/LegacyWifiPlatform.java @@ -0,0 +1,228 @@ +package com.alternadom.wifiiot.wifi; + +import android.net.Network; +import android.net.wifi.SupplicantState; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** Legacy STA backend for API < 29. */ +@SuppressWarnings("deprecation") // API < 29 +public final class LegacyWifiPlatform implements WifiPlatform { + private static final String UNKNOWN_SSID = ""; + private static final String HEX_EMPTY_SSID = "0x"; + + private final WifiManager wifiManager; + private final List ssidsToRemoveOnClose = new ArrayList<>(); + + public LegacyWifiPlatform(WifiManager wifiManager) { + this.wifiManager = wifiManager; + } + + @Override + public void connect(WifiConnectRequest request, WifiConnectCallback callback) { + callback.onSuccess( + connectLegacy( + request.ssid, + request.bssid, + request.password, + request.security, + request.joinOnce, + request.isHidden)); + } + + @Override + public boolean disconnect() { + return wifiManager.disconnect(); + } + + @Override + public void registerNetwork( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback) { + WifiConfiguration conf = createConfiguration(ssid, bssid, password, security, isHidden); + if (updateOrCreateNetwork(conf) == -1) { + callback.onError("Error", "Error updating network configuration", ""); + } else { + callback.onSuccess(); + } + } + + @Override + @NonNull + public WifiInfo getWifiInfo() { + return wifiManager.getConnectionInfo(); + } + + @Override + @Nullable + public String getSsid() { + return normalizeSsid(getWifiInfo().getSSID()); + } + + @Override + @Nullable + public String getIpv4() { + return formatIpv4(getWifiInfo().getIpAddress()); + } + + @Override + @Nullable + public Network getJoinedNetwork() { + return null; + } + + @Override + public void close() { + if (ssidsToRemoveOnClose.isEmpty()) { + return; + } + List configs = wifiManager.getConfiguredNetworks(); + if (configs == null) { + return; + } + for (String ssid : ssidsToRemoveOnClose) { + for (WifiConfiguration config : configs) { + if (config.SSID.equals(ssid)) { + wifiManager.removeNetwork(config.networkId); + } + } + } + ssidsToRemoveOnClose.clear(); + } + + private boolean connectLegacy( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean joinOnce, + @Nullable Boolean isHidden) { + WifiConfiguration conf = createConfiguration(ssid, bssid, password, security, isHidden); + int networkId = updateOrCreateNetwork(conf); + if (networkId == -1) { + return false; + } + + if (joinOnce != null && joinOnce) { + ssidsToRemoveOnClose.add(conf.SSID); + } + + if (!wifiManager.disconnect()) { + return false; + } + if (!wifiManager.enableNetwork(networkId, true)) { + return false; + } + + for (int i = 0; i < 20; i++) { + WifiInfo info = getWifiInfo(); + if (info.getNetworkId() != -1 && info.getSupplicantState() == SupplicantState.COMPLETED) { + return info.getNetworkId() == networkId; + } + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + break; + } + } + return false; + } + + private WifiConfiguration createConfiguration( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden) { + WifiConfiguration conf = new 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.equals("WPA")) { + conf.preSharedKey = "\"" + password + "\""; + conf.allowedProtocols.set(WifiConfiguration.Protocol.RSN); + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + conf.status = WifiConfiguration.Status.ENABLED; + conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); + conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); + conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + conf.allowedProtocols.set(WifiConfiguration.Protocol.RSN); + conf.allowedProtocols.set(WifiConfiguration.Protocol.WPA); + } else if (security.equals("WEP")) { + conf.wepKeys[0] = "\"" + password + "\""; + conf.wepTxKeyIndex = 0; + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); + } else { + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + } + return conf; + } + + private int updateOrCreateNetwork(WifiConfiguration conf) { + int updateNetwork = -1; + int registeredNetwork = -1; + List configs = wifiManager.getConfiguredNetworks(); + if (configs != null) { + for (WifiConfiguration existing : configs) { + if (existing.SSID.equals(conf.SSID) + && (existing.BSSID == null + || conf.BSSID == null + || existing.BSSID.equals(conf.BSSID))) { + conf.networkId = existing.networkId; + registeredNetwork = existing.networkId; + updateNetwork = wifiManager.updateNetwork(conf); + } + } + } + if (updateNetwork == -1) { + updateNetwork = wifiManager.addNetwork(conf); + wifiManager.saveConfiguration(); + } + return updateNetwork == -1 ? registeredNetwork : updateNetwork; + } + + @Nullable + static String normalizeSsid(@Nullable String ssid) { + if (ssid == null) { + return null; + } + if (ssid.length() >= 2 && ssid.charAt(0) == '"' && ssid.charAt(ssid.length() - 1) == '"') { + ssid = ssid.substring(1, ssid.length() - 1); + } + if (ssid.isEmpty() || UNKNOWN_SSID.equals(ssid) || HEX_EMPTY_SSID.equals(ssid)) { + return null; + } + return ssid; + } + + static String formatIpv4(int longIp) { + return (longIp & 0xff) + + "." + + ((longIp >> 8) & 0xff) + + "." + + ((longIp >> 16) & 0xff) + + "." + + ((longIp >> 24) & 0xff); + } +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java new file mode 100644 index 00000000..3f894a64 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java @@ -0,0 +1,442 @@ +package com.alternadom.wifiiot.wifi; + +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.MacAddress; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.TransportInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiNetworkSpecifier; +import android.net.wifi.WifiNetworkSuggestion; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** Modern STA backend for API 29+. */ +@RequiresApi(api = Build.VERSION_CODES.Q) // API 29+ +@SuppressWarnings("deprecation") // API < 31 +public final class ModernWifiPlatform implements WifiPlatform { + private static final String TAG = "ModernWifiPlatform"; + private static final int DEFAULT_TIMEOUT_MS = 30000; + + private final Context context; + private final WifiManager wifiManager; + private final ConnectivityManager connectivityManager; + private final Handler handler = new Handler(Looper.getMainLooper()); + + private ConnectivityManager.NetworkCallback networkCallback; + private List networkSuggestions; + @Nullable private Network joinedNetwork; + private final List suggestionsToRemoveOnClose = new ArrayList<>(); + + public ModernWifiPlatform( + Context context, WifiManager wifiManager, ConnectivityManager connectivityManager) { + this.context = context; + this.wifiManager = wifiManager; + this.connectivityManager = connectivityManager; + } + + @Override + public void connect(WifiConnectRequest request, WifiConnectCallback callback) { + if (request.security != null && request.security.toUpperCase().equals("WEP")) { + handler.post( + () -> + callback.onError( + "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, "")); + return; + } + + if (request.withInternet != null && request.withInternet) { + connectWithSuggestion(request, callback); + } else { + connectWithSpecifier(request, callback); + } + } + + private void connectWithSuggestion(WifiConnectRequest request, WifiConnectCallback callback) { + final WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder(); + builder.setSsid(request.ssid); + builder.setIsHiddenSsid(request.isHidden != null ? request.isHidden : false); + if (!applyBssid(builder, request.bssid, callback)) { + return; + } + if (request.security != null && request.security.toUpperCase().equals("WPA")) { + builder.setWpa2Passphrase(request.password); + } + + if (networkSuggestions != null) { + wifiManager.removeNetworkSuggestions(networkSuggestions); + } + + final WifiNetworkSuggestion suggestion = builder.build(); + networkSuggestions = new ArrayList<>(); + networkSuggestions.add(suggestion); + if (request.joinOnce != null && request.joinOnce) { + suggestionsToRemoveOnClose.add(suggestion); + } + + int status = wifiManager.addNetworkSuggestions(networkSuggestions); + if (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE) { + wifiManager.removeNetworkSuggestions(networkSuggestions); + status = wifiManager.addNetworkSuggestions(networkSuggestions); + } + Log.d(TAG, "addNetworkSuggestions status: " + status + " (suggestion_added_async if SUCCESS)"); + final boolean added = status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; + handler.post(() -> callback.onSuccess(added)); + } + + private void connectWithSpecifier(WifiConnectRequest request, WifiConnectCallback callback) { + final WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder(); + builder.setSsid(request.ssid); + builder.setIsHiddenSsid(request.isHidden != null ? request.isHidden : false); + if (!applyBssid(builder, request.bssid, callback)) { + return; + } + if (request.security != null && request.security.toUpperCase().equals("WPA")) { + builder.setWpa2Passphrase(request.password); + } + + final NetworkRequest networkRequest = + new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .setNetworkSpecifier(builder.build()) + .build(); + + unregisterNetwork(networkCallback); + networkCallback = null; + + Integer timeoutInSeconds = request.timeoutInSeconds; + int timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : DEFAULT_TIMEOUT_MS; + + final AtomicBoolean done = new AtomicBoolean(false); + + networkCallback = + new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(@NonNull Network network) { + super.onAvailable(network); + if (!done.compareAndSet(false, true)) { + return; + } + joinedNetwork = network; + connectivityManager.bindProcessToNetwork(network); + handler.post(() -> callback.onSuccess(true)); + } + + @Override + public void onUnavailable() { + super.onUnavailable(); + if (!done.compareAndSet(false, true)) { + return; + } + // Keep callback registered on API 30+ so a later user approval can still deliver + // onAvailable; on API 29, unregister to avoid leaks after terminal failure. + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + unregisterNetwork(this); + if (networkCallback == this) { + networkCallback = null; + } + } + handler.post(() -> callback.onSuccess(false)); + } + + @Override + public void onLost(Network network) { + super.onLost(network); + if (joinedNetwork != null && joinedNetwork.equals(network)) { + connectivityManager.bindProcessToNetwork(null); + joinedNetwork = null; + } + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + unregisterNetwork(this); + if (networkCallback == this) { + networkCallback = null; + } + } + } + }; + + connectivityManager.requestNetwork(networkRequest, networkCallback, handler, timeoutMs); + } + + private boolean applyBssid( + WifiNetworkSuggestion.Builder builder, @Nullable String bssid, WifiConnectCallback callback) { + if (bssid == null) { + return true; + } + MacAddress mac = parseMacAddress(bssid); + if (mac == null) { + handler.post(() -> callback.onError("Error", "Invalid BSSID representation", "")); + return false; + } + builder.setBssid(mac); + return true; + } + + private boolean applyBssid( + WifiNetworkSpecifier.Builder builder, @Nullable String bssid, WifiConnectCallback callback) { + if (bssid == null) { + return true; + } + MacAddress mac = parseMacAddress(bssid); + if (mac == null) { + handler.post(() -> callback.onError("Error", "Invalid BSSID representation", "")); + return false; + } + builder.setBssid(mac); + return true; + } + + @Override + public boolean disconnect() { + connectivityManager.bindProcessToNetwork(null); + joinedNetwork = null; + + boolean hasCallback = networkCallback != null; + unregisterNetwork(networkCallback); + networkCallback = null; + + boolean hasSuggestions = networkSuggestions != null; + boolean removed = true; + if (networkSuggestions != null) { + removed = + wifiManager.removeNetworkSuggestions(networkSuggestions) + == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; + networkSuggestions = null; + } + if (!suggestionsToRemoveOnClose.isEmpty()) { + wifiManager.removeNetworkSuggestions(suggestionsToRemoveOnClose); + suggestionsToRemoveOnClose.clear(); + } + + if (!hasCallback && !hasSuggestions) { + Log.e(TAG, "Can't disconnect from WiFi, no active callback/suggestions."); + return false; + } + return hasCallback || removed; + } + + @Override + public void registerNetwork( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { // API < 30 + new LegacyWifiPlatform(wifiManager) + .registerNetwork(ssid, bssid, password, security, isHidden, callback); + return; + } + createNetworkViaSettings(ssid, bssid, password, security, isHidden, callback); + } + + @RequiresApi(api = Build.VERSION_CODES.R) // API 30+ + private void createNetworkViaSettings( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback) { + final WifiNetworkSuggestion.Builder suggestedNet = new WifiNetworkSuggestion.Builder(); + suggestedNet.setSsid(ssid); + suggestedNet.setIsHiddenSsid(isHidden != null ? isHidden : false); + if (bssid != null) { + MacAddress macAddress = parseMacAddress(bssid); + if (macAddress == null) { + callback.onError("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")) { + callback.onError( + "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(Settings.EXTRA_WIFI_NETWORK_LIST, suggestionsList); + Intent intent = new Intent(Settings.ACTION_WIFI_ADD_NETWORKS); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + callback.onSuccess(); + } + + @Override + @NonNull + public WifiInfo getWifiInfo() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // API 31+ + WifiInfo wifiInfo = fetchWifiInfoFromConnectivityManager(); + if (wifiInfo != null) { + return wifiInfo; + } + } + return wifiManager.getConnectionInfo(); + } + + @Override + @Nullable + public String getSsid() { + return LegacyWifiPlatform.normalizeSsid(getWifiInfo().getSSID()); + } + + @Override + @Nullable + public String getIpv4() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // API 31+ + String ipv4 = fetchIpv4FromLinkProperties(); + if (ipv4 != null) { + return ipv4; + } + } + return LegacyWifiPlatform.formatIpv4(getWifiInfo().getIpAddress()); + } + + @Override + @Nullable + public Network getJoinedNetwork() { + return joinedNetwork; + } + + @Override + public void close() { + connectivityManager.bindProcessToNetwork(null); + joinedNetwork = null; + + unregisterNetwork(networkCallback); + networkCallback = null; + + if (networkSuggestions != null) { + wifiManager.removeNetworkSuggestions(networkSuggestions); + networkSuggestions = null; + } + + if (!suggestionsToRemoveOnClose.isEmpty()) { + wifiManager.removeNetworkSuggestions(suggestionsToRemoveOnClose); + suggestionsToRemoveOnClose.clear(); + } + } + + private void unregisterNetwork(@Nullable ConnectivityManager.NetworkCallback callback) { + if (callback == null) { + return; + } + try { + connectivityManager.unregisterNetworkCallback(callback); + } catch (IllegalArgumentException ignored) { + // already unregistered + } + } + + @Nullable + private static MacAddress parseMacAddress(@Nullable String bssid) { + if (bssid == null) { + return null; + } + try { + return MacAddress.fromString(bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Mac address parsing failed for bssid: " + bssid, e); + return null; + } + } + + @Nullable + @RequiresApi(api = Build.VERSION_CODES.S) // API 31+ + private WifiInfo fetchWifiInfoFromConnectivityManager() { + for (Network network : orderedWifiNetworks()) { + WifiInfo wifiInfo = fetchWifiInfoFromNetwork(network); + if (wifiInfo != null && LegacyWifiPlatform.normalizeSsid(wifiInfo.getSSID()) != null) { + return wifiInfo; + } + } + return null; + } + + @RequiresApi(api = Build.VERSION_CODES.M) // API 23+ + private List orderedWifiNetworks() { + List ordered = new ArrayList<>(); + if (joinedNetwork != null) { + ordered.add(joinedNetwork); + } + + List activeWifi = new ArrayList<>(); + List rest = new ArrayList<>(); + Network activeNetwork = connectivityManager.getActiveNetwork(); + + for (Network network : connectivityManager.getAllNetworks()) { + if (joinedNetwork != null && network.equals(joinedNetwork)) { + continue; + } + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + if (capabilities == null || !capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + continue; + } + if (activeNetwork != null && network.equals(activeNetwork)) { + activeWifi.add(network); + } else { + rest.add(network); + } + } + ordered.addAll(activeWifi); + ordered.addAll(rest); + return ordered; + } + + @Nullable + @RequiresApi(api = Build.VERSION_CODES.Q) // API 29+ + private WifiInfo fetchWifiInfoFromNetwork(Network network) { + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + if (capabilities == null) { + return null; + } + TransportInfo transportInfo = capabilities.getTransportInfo(); + return transportInfo instanceof WifiInfo ? (WifiInfo) transportInfo : null; + } + + @Nullable + @RequiresApi(api = Build.VERSION_CODES.S) // API 31+ + private String fetchIpv4FromLinkProperties() { + for (Network wifiNetwork : orderedWifiNetworks()) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(wifiNetwork); + if (linkProperties == null) { + continue; + } + for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { + InetAddress address = linkAddress.getAddress(); + if (address instanceof Inet4Address) { + return address.getHostAddress(); + } + } + } + return null; + } +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectCallback.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectCallback.java new file mode 100644 index 00000000..71433342 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectCallback.java @@ -0,0 +1,9 @@ +package com.alternadom.wifiiot.wifi; + +import androidx.annotation.Nullable; + +public interface WifiConnectCallback { + void onSuccess(boolean connected); + + void onError(String code, String message, @Nullable Object details); +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectRequest.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectRequest.java new file mode 100644 index 00000000..f68233c6 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectRequest.java @@ -0,0 +1,34 @@ +package com.alternadom.wifiiot.wifi; + +import androidx.annotation.Nullable; + +/** Parameters for a STA connect attempt. */ +public final class WifiConnectRequest { + public final String ssid; + @Nullable public final String bssid; + @Nullable public final String password; + @Nullable public final String security; + @Nullable public final Boolean joinOnce; + @Nullable public final Boolean withInternet; + @Nullable public final Boolean isHidden; + @Nullable public final Integer timeoutInSeconds; + + public WifiConnectRequest( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean joinOnce, + @Nullable Boolean withInternet, + @Nullable Boolean isHidden, + @Nullable Integer timeoutInSeconds) { + this.ssid = ssid; + this.bssid = bssid; + this.password = password; + this.security = security; + this.joinOnce = joinOnce; + this.withInternet = withInternet; + this.isHidden = isHidden; + this.timeoutInSeconds = timeoutInSeconds; + } +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiPlatform.java new file mode 100644 index 00000000..fe3dbd0c --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiPlatform.java @@ -0,0 +1,56 @@ +package com.alternadom.wifiiot.wifi; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * STA Wi‑Fi backend: {@link LegacyWifiPlatform} (API < 29) or {@link ModernWifiPlatform} (API + * 29+). + */ +public interface WifiPlatform { + void connect(WifiConnectRequest request, WifiConnectCallback callback); + + boolean disconnect(); + + void registerNetwork( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback); + + @NonNull + WifiInfo getWifiInfo(); + + @Nullable + String getSsid(); + + @Nullable + String getIpv4(); + + @Nullable + Network getJoinedNetwork(); + + void close(); + + interface RegisterCallback { + void onSuccess(); + + void onError(String code, String message, @Nullable Object details); + } + + static WifiPlatform create( + Context context, WifiManager wifiManager, ConnectivityManager connectivityManager) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return new LegacyWifiPlatform(wifiManager); + } + return new ModernWifiPlatform(context, wifiManager, connectivityManager); + } +} diff --git a/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java b/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java index 908c732f..663d0842 100644 --- a/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java +++ b/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java @@ -32,6 +32,7 @@ import java.net.InetAddress; import java.util.ArrayList; +@SuppressWarnings("deprecation") // API < 29 public class WifiApManager { private final WifiManager mWifiManager; private Context context; diff --git a/packages/wifi_iot/example/ios/Podfile b/packages/wifi_iot/example/ios/Podfile index e72e0b48..17ed9861 100644 --- a/packages/wifi_iot/example/ios/Podfile +++ b/packages/wifi_iot/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +# platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj b/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj index b292f06b..438e5b97 100644 --- a/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj @@ -370,7 +370,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -417,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/packages/wifi_iot/ios/wifi_iot.podspec b/packages/wifi_iot/ios/wifi_iot.podspec index 21281dda..2689478f 100644 --- a/packages/wifi_iot/ios/wifi_iot.podspec +++ b/packages/wifi_iot/ios/wifi_iot.podspec @@ -10,7 +10,7 @@ Flutter plugin which can handle WiFi connections and hotspot (AP, STA). DESC s.homepage = 'https://github.com/flutternetwork/WiFiFlutter' s.license = { :file => '../LICENSE' } - s.author = { 'WiFiFlutter' => 'harsh@bhikadia.com' } + s.author = { 'WiFiFlutter' => 'contact@flutternetwork.dev' } s.source = { :path => '.' } s.source_files = 'wifi_iot/Sources/wifi_iot/**/*.swift' s.dependency 'Flutter' diff --git a/packages/wifi_iot/pubspec.yaml b/packages/wifi_iot/pubspec.yaml index 1edb6569..e3bfb4e6 100644 --- a/packages/wifi_iot/pubspec.yaml +++ b/packages/wifi_iot/pubspec.yaml @@ -1,6 +1,6 @@ name: wifi_iot description: Flutter plugin which can handle WiFi connections and hotspot (AP, STA) -version: 0.4.0 +version: 0.4.1-dev.0 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_iot flutter: diff --git a/packages/wifi_scan/CHANGELOG.md b/packages/wifi_scan/CHANGELOG.md index 12952c96..b54b3c7c 100644 --- a/packages/wifi_scan/CHANGELOG.md +++ b/packages/wifi_scan/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.1-dev.0 + + - **FEAT**(wifi_scan): Implement Android modern Wi‑Fi scan API. + ## 0.5.0 > Note: This release has breaking changes. diff --git a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt index b6c1b6ec..1655796e 100644 --- a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt +++ b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt @@ -96,9 +96,14 @@ class WifiScanPlugin : } } } - val intentFilter = IntentFilter() - intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) - context.registerReceiver(wifiScanReceiver, intentFilter) + val intentFilter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) + // API 33+: RECEIVER_NOT_EXPORTED + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(wifiScanReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + } else { + @Suppress("DEPRECATION") // API < 33 + context.registerReceiver(wifiScanReceiver, intentFilter) + } // set Flutter channels - 1 for method, 1 for event channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wifi_scan") @@ -228,9 +233,9 @@ class WifiScanPlugin : val hasLocPerm = hasLocationPermission() val isLocEnabled = isLocationEnabled() return when { - // for SDK < P[28] : Not in guide, should not require any additional permissions + // API < 28 Build.VERSION.SDK_INT < Build.VERSION_CODES.P -> CAN_START_SCAN_YES - // for SDK >= Q[29]: CHANGE_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" + // API 29+ hasLocPerm && isLocEnabled -> CAN_START_SCAN_YES hasLocPerm -> CAN_START_SCAN_NO_LOC_DISABLED askPermission -> ASK_FOR_LOC_PERM @@ -238,11 +243,10 @@ class WifiScanPlugin : } } + @Suppress("DEPRECATION") // API 29+ throttled private fun startScan(): Boolean = wifi!!.startScan() private fun canGetScannedResults(askPermission: Boolean): Int { - // check all prerequisite conditions - // ACCESS_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" val hasLocPerm = hasLocationPermission() val isLocEnabled = isLocationEnabled() return when { @@ -256,7 +260,7 @@ class WifiScanPlugin : private fun getScannedResults(): List> = wifi!!.scanResults.map { ap -> mapOf( - "ssid" to ap.SSID, + "ssid" to ssidFromScanResult(ap), "bssid" to ap.BSSID, "capabilities" to ap.capabilities, "frequency" to ap.frequency, @@ -275,19 +279,34 @@ class WifiScanPlugin : "isPasspoint" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, "operatorFriendlyName" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.operatorFriendlyName - else null, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + @Suppress("DEPRECATION") // API < 31 + ap.operatorFriendlyName?.toString() + } else null, "venueName" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.venueName else null, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + @Suppress("DEPRECATION") // API < 31 + ap.venueName?.toString() + } else null, "is80211mcResponder" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null) } + /** API 33+: wifiSsid; API < 33: SSID. */ + private fun ssidFromScanResult(ap: android.net.wifi.ScanResult): String? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val wifiSsid = ap.wifiSsid ?: return null + return wifiSsid.toString().trim('"') + } + @Suppress("DEPRECATION") // API < 33 + return ap.SSID + } + private fun onScannedResultsAvailable() { eventSink?.success(getScannedResults()) } - /** ACCESS_FINE_LOCATION required for: SDK >= Q[29] and tSDK >= Q[29] */ + /** API 29+ and targetSdk 29+: fine location required. */ private fun requiresFineLocation(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q @@ -311,11 +330,9 @@ class WifiScanPlugin : } private fun askForLocationPermission(callback: (AskLocPermResult) -> Unit) { - // check if has activity - return error if null if (activity == null) return callback.invoke(AskLocPermResult.ERROR_NO_ACTIVITY) - // make permissions val requiresFine = requiresFineLocation() - // - for SDK > R[30] - cannot only ask for FINE + // API > 30: ask FINE + COARSE together val requiresFineButAskBoth = requiresFine && Build.VERSION.SDK_INT > Build.VERSION_CODES.R val permissions = when { @@ -323,18 +340,14 @@ class WifiScanPlugin : requiresFine -> locationPermissionFine else -> locationPermissionCoarse } - // request permission - add result-handler in requestPermissionCookie val permissionCode = 6567800 + Random.Default.nextInt(100) requestPermissionCookie[permissionCode] = { grantArray -> - // invoke callback with proper askResult Log.d(logTag, "permissionResultCallback: args($grantArray)") callback.invoke( when { - // GRANTED: if all granted grantArray.all { it == PackageManager.PERMISSION_GRANTED } -> { AskLocPermResult.GRANTED } - // UPGRADE_TO_FINE: if requiresFineButAskBoth and COARSE granted requiresFineButAskBoth && grantArray.first() == PackageManager.PERMISSION_GRANTED -> { AskLocPermResult.UPGRADE_TO_FINE } diff --git a/packages/wifi_scan/example/ios/Podfile b/packages/wifi_scan/example/ios/Podfile index e72e0b48..17ed9861 100644 --- a/packages/wifi_scan/example/ios/Podfile +++ b/packages/wifi_scan/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +# platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj b/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj index 34e0ddd6..f4760929 100644 --- a/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj @@ -339,7 +339,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -417,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -466,7 +466,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/wifi_scan/pubspec.yaml b/packages/wifi_scan/pubspec.yaml index 3612c3ff..b117addb 100644 --- a/packages/wifi_scan/pubspec.yaml +++ b/packages/wifi_scan/pubspec.yaml @@ -1,6 +1,6 @@ name: wifi_scan description: Flutter plugin to scan for nearby visible WiFi access points. -version: 0.5.0 +version: 0.5.1-dev.0 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_scan environment: