Is it possible to add a network configuration on Android Q?
Asked Answered
D

4

17

Background

I've noticed that in WifiManager class there is a function called addNetwork, that might be useful if I want to restore or save networks information (network name AKA SSID, together with the password and the type), so that I could also connect to it.

The problem

I can't find much information about how to do such a thing. I've seen various examples on StackOverflow, and if I target Android API 28 (or below), I indeed succeed to make it add a network and even connect to it.

When targeting Android 29 (Android Q), however, it fails to add the network.

What I've found

Since I'm trying on Pixel 2 with Android Q beta 4, I think that maybe it's because addNetwork is deprecated, so the docs even say so, and that if I target Android Q, it won't work, and indeed it doesn't work:

Compatibility Note: For applications targeting Build.VERSION_CODES.Q or above, this API will always return -1.

The way it seems it should work up till Android Q (excluding), is by preparing WifiConfiguration and adding it. Later I can also connect to it if I wish. On Android Q, it seems it was replaced by WifiNetworkSuggestion, but it doesn't seem like it's about adding a network at all:

The Network Suggestion object is used to provide a Wi-Fi network for consideration when auto-connecting to networks. Apps cannot directly create this object, they must use WifiNetworkSuggestion.Builder#build() to obtain an instance of this object.

Apps can provide a list of such networks to the platform using WifiManager#addNetworkSuggestions(List).

Here's my current code, for pre-Android-Q

@WorkerThread
fun addNetwork(context: Context, networkName: String, networkPassword: String? = null, keyMgmt: Int = WifiConfiguration.KeyMgmt.NONE) {
    val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
    val conf = WifiConfiguration()
    conf.SSID = "\"$networkName\""
    conf.preSharedKey = if (networkPassword.isNullOrEmpty()) "" else "\"$networkPassword\""
    conf.allowedKeyManagement.set(keyMgmt)
    when (keyMgmt) {
        WifiConfiguration.KeyMgmt.WPA_PSK -> {
            //WPA/WPA2
        }
        WifiConfiguration.KeyMgmt.IEEE8021X -> {
        }
        WifiConfiguration.KeyMgmt.WPA_EAP -> {
        }
        WifiConfiguration.KeyMgmt.NONE -> {
            if (networkPassword.isNullOrEmpty()) {
                //open network
                conf.wepKeys[0] = "\"\""
            } else {
                //wep
                conf.wepKeys[0] = "\"" + networkPassword + "\""
                conf.wepTxKeyIndex = 0
                conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40)
            }
        }
    }
    if (networkPassword.isNullOrEmpty()) {
        //open network
        conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
    } else {
    }
    wifiManager.isWifiEnabled = true
    while (!wifiManager.pingSupplicant()) {
        Log.d("AppLog", "waiting to be able to add network")
    }
    val networkId = wifiManager.addNetwork(conf)
    if (networkId == -1)
        Log.d("AppLog", "failed to add network")
    else {
        wifiManager.enableNetwork(networkId, false)
        Log.d("AppLog", "success to add network")
    }
}

Seems it requires only these permissions:

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>

But in any case, this works as long as you don't target Android Q (API 29) and above. When you target it, I indeed always get "-1" as a result, meaning it fails.

I've also found an issue on the issue tracker (here and I wrote about it here), telling about someone that needs the API back, but I'm not sure it's about adding a network.

Looking at WifiNetworkSuggestion, I don't see that it has as many things to set as WifiConfiguration via its builder, so this is another reason for why I suspect it's not about adding a network.

But I tried anyway. Here's the code I've tried, for example, to add a normal WPA network:

@WorkerThread
fun addNetworkAndroidQ(context: Context, networkName: String, networkPassword: String? = null) {
    val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
    val list = ArrayList<WifiNetworkSuggestion>()
    val builder = WifiNetworkSuggestion.Builder().setSsid(networkName)
    if (!networkPassword.isNullOrEmpty())
        builder.setWpa2Passphrase(networkPassword)
    list.add(builder.build())
    val result = wifiManager.addNetworkSuggestions(list)
    if (result == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS)
        Log.d("AppLog", "success")
    else Log.d("AppLog", "failed")
}

When running (I gave it my Wifi network details, after making the OS forget about it), it says it succeeded, but nothing occurred on the OS's Wifi settings. The network doesn't exist there with the password I've added. So I really don't get what it did...

After a few long seconds, I've noticed a notification asking me if it's ok to connect to the suggested networks made by the app:

enter image description here

But still when I chose that I accept, it didn't do anything, as before.

I tried to make another POC, thinking I might have done it incorrectly, but then it didn't even show the notification. Since I think this whole behavior is a bug, I've reported about it here.

Not only that, but I've found out that if indeed it is supposed to add a network one way or another, it still has some serious restrictions, such as max added networks (here) and being removed upon uninstall of the app (here)

The questions

  1. How should Android Q be handled exactly? Is there really no API anymore to add a network?

  2. If WifiNetworkSuggestion is not about adding a network, what is it really used for exactly?

  3. Since I'm not familiar enough with the tidbits of adding a network, is my code correct about all possible ways to add a network? I ask this because someone wrote here that people should enable Wifi and make sure pingSupplicant returns true. Is it true? Or would it be enough to just call addNetwork ?

  4. If it's now impossible to add a network using the normal API, is there maybe a solution by using a rooted device instead? Maybe some adb command?


EDIT: Not sure how to do it officially, but using adb, you might be able to add Wifi-networks on Android 11 . Need to check adb shell cmd wifi help .

Doubler answered 5/7, 2019 at 15:39 Comment(10)
Any updates on this one? I am having same issues, and I have seen some apps targeting API 29 connecting to its wifi configuration without such notifications/prompts.Polyhydroxy
@AswinPAshok According to what I've read, Google plans to make some more flexibility for this API on Android R. Probably allowing to set it as permanent (staying after app-removal) .Doubler
Can you take a look at this image: imgur.com/cdv3fHR , After this prompt, some apps are able to connect to their wifi configurations. What could this intent be? Sorry for being stupid.Polyhydroxy
@AswinPAshok I have no idea. I suggest to ask a new question. I only tested this API out of being curious, and because I want to have this functionality on third party apps :)Doubler
@androiddeveloper how can I set Proxy options such as hostname and proxy port? and IP Settings?Hanuman
@AnantShah Sorry I have no idea. I suggest to play with the API and see what's being offered.Doubler
@androiddeveloper it seems many classes are annotated with "UnsupportedAppUsage" like android.net.IpConfiguration, android.net.StaticIpConfiguration not able to find any documentation or example to set wifiConfiguration fieldsHanuman
@androiddeveloper I also checked AOSP code for wifi settings app to set wifi configuration in Android Q. But they are using WifiConfigController.java class and line no 1264 has code written which has unsupported app usage annotation StaticIpConfiguration used.Hanuman
Not sure if could help: github.com/aosp-mirror/platform_packages_apps_settings/blob/…Doubler
Again, sorry, I can't help you.Doubler
P
10

I stuck with same issue, but somehow I reached a reproducible state for connecting a desired network and I want to share my findings it may helps.

As a summary: You have to disable all auto connection before applying WifiNetworkSuggestion logic

For more details, Please read the following:

I used the following code (Similar to what you use):

private fun connectUsingNetworkSuggestion(ssid: String, password: String) {
    val wifiNetworkSuggestion = WifiNetworkSuggestion.Builder()
        .setSsid(ssid)
        .setWpa2Passphrase(password)
        .build()

    // Optional (Wait for post connection broadcast to one of your suggestions)
    val intentFilter =
        IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);

    val broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (!intent.action.equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
                return
            }
            showToast("Connection Suggestion Succeeded")
            // do post connect processing here
        }
    }
    registerReceiver(broadcastReceiver, intentFilter)

    lastSuggestedNetwork?.let {
        val status = wifiManager.removeNetworkSuggestions(listOf(it))
        Log.i("WifiNetworkSuggestion", "Removing Network suggestions status is $status")
    }
    val suggestionsList = listOf(wifiNetworkSuggestion)
    var status = wifiManager.addNetworkSuggestions(suggestionsList)
    Log.i("WifiNetworkSuggestion", "Adding Network suggestions status is $status")
    if (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE) {
        showToast("Suggestion Update Needed")
        status = wifiManager.removeNetworkSuggestions(suggestionsList)
        Log.i("WifiNetworkSuggestion", "Removing Network suggestions status is $status")
        status = wifiManager.addNetworkSuggestions(suggestionsList)
    }
    if (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
        lastSuggestedNetwork = wifiNetworkSuggestion
        lastSuggestedNetworkSSID = ssid
        showToast("Suggestion Added")
    }
}

So here are the steps:

  1. Install fresh version / Or remove all suggestion you added before
  2. Make sure that you forgot all surrounding networks so your device won't auto-connect
  3. Add wifi network suggestions list
  4. Go to Wifi Settings to scan networks Or wait until next scan is running
  5. A notification prompt will appear :

Notification prompt 6. When you Press "Yes" the system will auto-connect with it via your app and internet will work normally. See the following:

Connected to Suggested Network

Please note the following:

  1. If you disconnect the network from Wifi Settings (i.e press disconnect bin icon in the following image) your network will be blocked for 24 hours from auto-connect even if you removed the suggested network using wifiManager.removeNetworkSuggestions(listOf(it)) and add it again. And even if you uninstall and install your app again

Connected Wifi details

Unfortunately, this is limitation added by Android System as described here:

If the user uses the Wi-Fi picker to explicitly disconnect from one of the network suggestions when connected to it, then that network is blacklisted for 24 hours. During the blacklist period, that network will not be considered for auto-connection, even if the app removes and re-adds the network suggestion corresponding to the network.

  1. If you uninstall the application while connected to suggested WiFi, the system will close the connection automatically.
  2. In case you have multiple suggestion you can priorities them by using WifiNetworkSuggestion.Builder().setPriority(<Priority Integer>) as mentioned here:

Specify the priority of this network among other network suggestions provided by the same app (priorities have no impact on suggestions by different apps). The higher the number, the higher the priority (i.e value of 0 = lowest priority).

  1. In case you pressed "No" in notification prompt, you can change it from (Settings > Apps & notifications > Special App access > Wi-Fi Control > App name) as described here:

A user declining the network suggestion notification removes the CHANGE_WIFI_STATE permission from the app. The user can grant this approval later by going into the Wi-Fi control menu (Settings > Apps & notifications > Special App access > Wi-Fi Control > App name).

Protozoology answered 16/4, 2020 at 23:29 Comment(2)
Thanks for sharing the answer its really helpful. But I am stuck in a way that removeNetworkSuggestions method doesn't really disconnect me from the wifi I have connected using networkSuggestion. Only uninstalling the app disconnects me from the wifi. Any other method to disconnect?Wholism
Nice solution but, is it possible to save a network configuration in Android 10 (API 29)?Sidero
T
5

Looks like they've added support in Android 11(API 30) for adding network configuration that persists outside of the application scope and is saved as a system network configuration just like it was done with the deprecated WiFiManager method addNetwork. All you need to do is to use ACTION_WIFI_ADD_NETWORKS to show a system dialog that asks a user if he wants to proceed with adding a new Wifi suggestion to the system. This is how we start that dialog:

// used imports
import android.provider.Settings.ACTION_WIFI_ADD_NETWORKS
import android.provider.Settings.EXTRA_WIFI_NETWORK_LIST
import android.app.Activity
import android.content.Intent
import android.net.wifi.WifiNetworkSuggestion





// show system dialog for adding new network configuration
    val wifiSuggestionBuilder = WifiNetworkSuggestion.Builder()
                .setSsid("network SSID")
                .build()

    val suggestionsList = arraylistOf(wifiSuggestionBuilder)
    val intent = new Intent(ACTION_WIFI_ADD_NETWORKS)
    intent.putParcelableArrayListExtra(EXTRA_WIFI_NETWORK_LIST, suggestionsList);
    activity.startActivityForResult(intent, 1000)

The dialog looks like this:

And then we just need to handle a result in onActivityResult method like this:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == 1000) {
        if (resultCode == Activity.RESULT_OK) {
            // network succesfully added - User pressed Save
        } else if (resultCode == Activity.RESULT_CANCELED) {
            // failed attempt of adding network to system - User pressed Cancel
        }
    }
}

But as I've tested this code on Android devices that have older Android versions(lower then API30) installed I've got a crash every time I want it to show that dialog for adding a new network configuration. This is the crash:

java.lang.RuntimeException: Unable to start activity: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.settings.WIFI_ADD_NETWORKS (has extras) }

Looks like the new way is not back-supported out of the box. So, for API30 we can use a new Intent action, for API 28 and below we can still use the old way of adding Networks, but for API29 we have some kind of gray area where I was not able to find a good solution yet. If anyone has an idea what else to do please share it with me. ;)

Triangular answered 7/10, 2020 at 14:5 Comment(2)
Thank you. About why it doesn't work on API 29, it's because ACTION_WIFI_ADD_NETWORKS is from API 30 : developer.android.com/reference/android/provider/…Doubler
What is the solution that you did on API lvl 29?Spew
H
4

I wish I had answers to all of your questions because I'm currently struggling with similar issues.

After many hours I was finally able to connect to the desired network using this approach:

val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
    .setSsid(ssid)
    .setWpa2Passphrase(passphrase)
    .setBssid(mac)
    .build()

val networkRequest = NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    .setNetworkSpecifier(wifiNetworkSpecifier)
    .build()

val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?

connectivityManager?.requestNetwork(networkRequest, ConnectivityManager.NetworkCallback())

You can receive a whole host of events through the ConnectivityManager.NetworkCallback().

Higginson answered 2/9, 2019 at 22:4 Comment(9)
Wait, what is this API? Is this like the old one? Is this without restrictions? Without "suggesting"? Does it add the network forever? It stays after removal?Doubler
This is API introduced in Android Q (10). As far as I can tell this connection lasts while the NetworkCallback is not removed. The network isn't saved in network configurations.Higginson
So it's about the same as the API I've found, no? It gets removed when the app is removed? What's the difference between this and what I've found? Does it ask the user to add it?Doubler
The API mentioned above is created for conencting to a network that does not provide the internet connection (eg. external device like chromecast or camera), unlike the WiFi suggestion API. Using the above API, user will get a popup with selected SSID so he can confirm connecting to it. User will get conencted to that network, but the connection is available only inside the app (it won't work in external browser like Chrome).Foliole
@SebastianHelzer Can you confirm the comment from Mohru that the internet is not working for connections you established over the WifiNetworkSpecifier method? I think the connection is also restricted to your own app, so others app won't be able to use it.Weinberg
@JulianSchweppe Mohru is correct the connection is only available inside the app and is useful for communicating with external devices.Higginson
I was able to connect successfully using the NetworkRequest you have in your code snippet, but I don't have internet connection, not even within my app. I see a wifi icon in status bar with "x" in the bottom part indicating no internet. Is there some configuration that I could be missing?Zizith
@Foliole Other app can still access the wifi network you requested via use this: val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { // bind this so all api calls are performed over this new network connectManager.bindProcessToNetwork(network) onWifiConnected() } }Encroach
@VũHồngKỳ Probably they can, but you have to put this code directly in the app. You won't be able to do this with existing applications like Chrome.Foliole
E
1

@Sebastian Helzer's answer works for me. I use java in my application. This may help java users...

WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
                .setSsid(ssid)
                .setWpa2Passphrase(password)
                .build();
NetworkRequest networkRequest = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .setNetworkSpecifier(wifiNetworkSpecifier)
                .build();
ConnectivityManager connectivityManager = (ConnectivityManager)this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback());
Eschew answered 11/8, 2020 at 8:42 Comment(3)
Did you succeed in connecting a desired network? What about the internet connection? Is it working?Blowout
successfully connected to desired network but internet connection not established.Eschew
Any luck @AmanullahAsraf in getting internet connection established using your method, even just for your app?Zizith

© 2022 - 2024 — McMap. All rights reserved.