4

I'm working on a Wifi auto connect feature and I am shocked how broken that API is.

I'm using now 5 different APIs and I still don't get it in a way the user would expect it.

I have a setting to enable wifi auto connection on Android 10+ I'll try this:

  1. Check if I hold the ACCESS_WIFI_STATE permission with:
    appOpsManager.unsafeCheckOp("android:change_wifi_state", Process.myUid(), context.packageName) == AppOpsManager.MODE_ALLOWED
    
    When I own the permission I go on with 2. if not I continue with 6.
  2. When I hold the permission I'll check on Android 11+ if from a previous run my wifi suggestion was saved wifiManager.networkSuggestions.isNotEmpty() if that is true I check which Wifi I'm currently connected with see step x. On lower levels I skip this and go to step 3
  3. I use the wifi suggestion API with the WifiNetworkSuggestion.Builder() and suggest the user my wifi with
    val status = wifiManager.addNetworkSuggestions(listOf(suggestion))
    // Strage bug: On first usage the return value is always SUCCESS
    val success = status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS
    
  4. When the user accepts my suggestion I'm fine so far. I cannot verify on Android 10 if my suggestion was accepted. On Android 11 this is possible (see step 2). However I still don't know if the device actually connected with the wifi. So I go to step 7
  5. When the user rejected my suggestion I'm almost out of the game I can never ask the user again (well technically there is a well hidden feature where you can switch the permission, but no user will ever find it). On Android 10 I can just check if I'm connected with the right wifi, well at least theoretically (see step 7).
  6. On Android 11+ I can fallback to the Settings.ACTION_WIFI_ADD_NETWORKS intent which can always add Wifis (with the downside that the user can share the wifi password)
  7. I need to verify if the steps before worked and the user is actually connected to the desired wifi. To do so I already own the permissions ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE, ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION. However the ConnectivityManager API don't return the actual SSID, that drives me crazy.
  8. If the user is not connected to the right wifi I would try to use the Settings.Panel.ACTION_WIFI action to let the user to connect to the right wifi

To summarize that:

  • I use the WifiNetworkSuggestion API
  • I fallback to Settings.ACTION_WIFI_ADD_NETWORKS where possible
  • I try to verify if the user is currently connected to the right wifi
  • I try to assist the user to conenct to the right wifi (or even turn wifi on) with the Settings.Panel.ACTION_WIFI action

Is that really such a mess or is there a easier way?


I'm currently accessing the SSID like this:

private val currentSsid: String?
    get() =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val wifiInfo = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)?.transportInfo as? WifiInfo
            wifiInfo?.ssid
        } else {
            wifiManager.connectionInfo?.ssid
        }

Based on Unable to get WIFI SSID using onCapabilitiesChanged in Android 12 I think that way I access the value is not supported. I get currently the value "<unknown ssid>".

rekire
  • 47,260
  • 30
  • 167
  • 264
  • I'm uncertain why you have step 1. `ACCESS_WIFI_STATE` is a `normal` permission, one that has been around since Android 1.0. Outside of rooted devices or crazy ROMs, if your app is installed and running, you have the permission. Are there scenarios where you see otherwise? – CommonsWare Mar 20 '22 at 19:22
  • "However the ConnectivityManager API don't return the actual SSID, that drives me crazy" -- they do for me, admittedly with `ACCESS_FINE_LOCATION`. Note that depending on circumstance, you may get `null` for the SSID, specifically if you are connecting to a network without Internet access and the user has not accepted the notification that alerts them of this fact. Once they accept the notification, you should get the SSID. – CommonsWare Mar 20 '22 at 19:22
  • Hey @CommonsWare I'm glad that I got your attention. Regarding the `ACCESS_WIFI_STATE` permission check this bug: https://issuetracker.google.com/issues/224071894 it seems that when you press on "no thanks" that this permission is revoked (the way to change this is documented there too) – rekire Mar 20 '22 at 19:42
  • Regarding the wifi ssid I get the value `""`. See [Unable to get WIFI SSID using onCapabilitiesChanged in Android 12](https://stackoverflow.com/q/68665456/995926) I think that way I access the value is not supported. I'll try now that broadcast receiver with that flag. – rekire Mar 20 '22 at 19:44
  • On the former, thanks! That's new (and disturbing), but I'm not using the suggestion API just yet (though I may be soon). On the latter, that might be the didn't-accept-the-notification value instead of `null` -- it has been a bit since I poked at this scenario. I know that there's an interim value until the user accepts that notification. However, I'm not using `onCapabilitiesChanged()` in a callback; perhaps `getNetworkCapabilities()` behaves a bit differently, somehow. – CommonsWare Mar 20 '22 at 19:52
  • I did not verify when they changed the behavior, but on Android 12 that is a nice dialog. Which is actual helpful out of my view. I'm working for a full week on that feature and I'm working with android since the RC times of Android 1.0 – rekire Mar 20 '22 at 19:57

1 Answers1

3

Well just a half answer, but it might help anyway. Here is how I get the current SSID of the user (you need to hold the location permission):

fun updateSsid(networkCapabilities: NetworkCapabilities) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        currentSsid = (networkCapabilities.transportInfo as? WifiInfo)?.ssid?.trim('"')
    }
}

wifiCallback = when {
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
        object: ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
            override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
                updateSsid(networkCapabilities)
            }
        }
    }
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
        object: ConnectivityManager.NetworkCallback() {
            override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
                updateSsid(networkCapabilities)
            }
        }
    }
    else -> null
}

wifiCallback?.let {
    fragment.lifecycle.addObserver(object: DefaultLifecycleObserver {
        override fun onResume(owner: LifecycleOwner) {
            connectivityManager.registerNetworkCallback(request, it)
        }

        override fun onPause(owner: LifecycleOwner) {
            connectivityManager.unregisterNetworkCallback(it)
        }
    })
}

So basically aspects of your code need to check apis for the levels <10, 10, 11 and 12. What a mess.

This might be quiet handy if you want the user to pick the configured wifi:

fun showWifiDialog() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        try {
            fragment.startActivity(Intent(android.provider.Settings.Panel.ACTION_WIFI))
        } catch (e: Exception) {
            // ignored
        }
    }
}
rekire
  • 47,260
  • 30
  • 167
  • 264