16

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 .

4

4 回答 4

8

我遇到了同样的问题,但不知何故,我达到了连接所需网络的可重现状态,我想分享我的发现,这可能会有所帮助。

总结:WifiNetworkSuggestion您必须在应用逻辑 之前禁用所有自动连接

更多详情,请阅读以下内容:

我使用了以下代码(类似于您使用的代码):

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")
    }
}

所以这里是步骤:

  1. 安装新版本/或删除您之前添加的所有建议
  2. 确保您忘记了所有周围的网络,这样您的设备就不会自动连接
  3. 添加wifi网络建议列表
  4. 转到 Wifi 设置以扫描网络或等到下一次扫描运行
  5. 将出现通知提示:

通知提示 6.当您按下“是”时,系统将通过您的应用程序自动连接它并且互联网将正常工作。请参阅以下内容:

连接到建议的网络

请注意以下事项:

  1. 如果您从 Wifi 设置中断开网络(即按下下图中的断开连接图标),即使您删除了建议的网络wifiManager.removeNetworkSuggestions(listOf(it))并再次添加,您的网络也将被阻止 24 小时自动连接。即使您再次卸载并安装您的应用程序

已连接 Wifi 详情

不幸的是,这是 Android 系统添加的限制,如下所述

如果用户在连接到某个网络建议时使用 Wi-Fi 选择器明确断开与它的连接,则该网络将被列入黑名单 24 小时。在黑名单期间,即使应用删除并重新添加与网络对应的网络建议,也不会考虑自动连接该网络。

  1. 如果您在连接到建议的 WiFi 时卸载应用程序,系统将自动关闭连接。
  2. 如果您有多个建议,您可以使用这里WifiNetworkSuggestion.Builder().setPriority(<Priority Integer>)提到的优先级:

在同一应用提供的其他网络建议中指定此网络的优先级(优先级对不同应用的建议没有影响)。数字越大,优先级越高(即值 0 = 最低优先级)。

  1. 如果您在通知提示中按下“否”,您可以(Settings > Apps & notifications > Special App access > Wi-Fi Control > App name)按照此处所述进行更改:

拒绝网络建议通知的用户会从应用中删除 CHANGE_WIFI_STATE 权限。用户可以稍后通过进入 Wi-Fi 控制菜单(设置 > 应用程序和通知 > 特殊应用程序访问 > Wi-Fi 控制 > 应用程序名称)来授予此批准。

于 2020-04-16T23:29:11.920 回答
4

我希望我能回答你所有的问题,因为我目前正在努力解决类似的问题。

几个小时后,我终于能够使用这种方法连接到所需的网络:

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())

您可以通过ConnectivityManager.NetworkCallback().

于 2019-09-02T22:04:57.350 回答
4

看起来他们已经在 Android 11(API 30) 中添加了对添加网络配置的支持,该网络配置在应用程序范围之外持续存在并保存为系统网络配置,就像使用已弃用的 WiFiManager 方法addNetwork一样。您需要做的就是使用ACTION_WIFI_ADD_NETWORKS显示一个系统对话框,询问用户是否要继续向系统添加新的 Wifi 建议。这就是我们启动该对话框的方式:

// 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)

对话框如下所示:

然后我们只需要在这样的onActivityResult方法中处理结果:

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
        }
    }
}

但是,当我在安装了较旧 Android 版本(低于 API30)的 Android 设备上测试此代码时,每次我希望它显示用于添加新网络配置的对话框时都会崩溃。这是崩溃:

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

看起来新方法没有开箱即用的支持。因此,对于 API30,我们可以使用新的 Intent 操作,对于 API 28 及以下版本,我们仍然可以使用添加网络的旧方法,但对于 API29,我们有一些灰色区域,我还没有找到好的解决方案。如果有人知道还能做什么,请与我分享。;)

于 2020-10-07T14:05:25.387 回答
1

@Sebastian Helzer的回答对我有用。我在我的应用程序中使用 java。这可能会帮助java用户......

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());
于 2020-08-11T08:42:27.143 回答