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