Android WiFi Manager enableNetwork返回true,但NetworkInfo状态始终为DISCONNECTED / SCANNING

时间:2018-12-10 20:53:10

标签: android rx-java2 wifimanager

我已经编写了一些代码来启用具有给定networkId的网络,并通过BroadcastReceiver处理异步响应。但是,即使enableNetwork返回true(指示操作系统已成功发出命令),我的BroadcastReceiver也从未收到状态为NetworkInfo的{​​{1}},它仍然收到2个事件:{{1 }},然后CONNECTED

从我读过的所有官方文档和各种SO问题开始,如果DISCONNECTED返回true,则为处理DISCONNECTED/SCANNING意图而注册的enableNetwork应该总是收到一个BroadcastReceiver对象状态为NETWORK_STATE_CHANGED_ACTION

代码如下:

NetworkInfo

有人可以帮忙吗?我真的很为难。我要传递的CONNECTED有效,因为它是根据/** * Connects to the wifi access point at specified [ssid] with specified [networkId] * And returns the [WifiInfo] of the network that has been connected to */ private fun connect(context: Context, wifiManager: WifiManager, ssid: String, networkId: Int) = Single.create<WifiInfo> { emitter -> val wifiConnectionReceiver = object : BroadcastReceiver() { var oldSupplicantState: SupplicantState? = null override fun onReceive(context: Context, intent: Intent) { if (intent.action == WifiManager.NETWORK_STATE_CHANGED_ACTION) { val networkInfo = intent.getParcelableExtra<NetworkInfo>(WifiManager.EXTRA_NETWORK_INFO) ?: return if (networkInfo.detailedState == NetworkInfo.DetailedState.DISCONNECTED) { context.applicationContext.unregisterReceiver(this) emitter.onError(WiFiException("Failed to connect to wifi network")) } else if (networkInfo.detailedState == NetworkInfo.DetailedState.CONNECTED) { val wifiInfo = intent.getParcelableExtra<WifiInfo>(WifiManager.EXTRA_WIFI_INFO) ?: return if (ssid == wifiInfo.ssid.unescape()) { context.applicationContext.unregisterReceiver(this) emitter.onSuccess(wifiInfo) } } } else if (intent.action == WifiManager.SUPPLICANT_STATE_CHANGED_ACTION) { val supplicantState = intent.getParcelableExtra<SupplicantState>(WifiManager.EXTRA_NEW_STATE) val oldSupplicantState = this.oldSupplicantState this.oldSupplicantState = supplicantState if (supplicantState == SupplicantState.DISCONNECTED) { if (oldSupplicantState == null || oldSupplicantState == SupplicantState.COMPLETED) { return } val possibleError = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1) if (possibleError == WifiManager.ERROR_AUTHENTICATING) { context.applicationContext.unregisterReceiver(this) emitter.onError(WiFiException("Wifi authentication failed")) } } else if (supplicantState == SupplicantState.SCANNING && oldSupplicantState == SupplicantState.DISCONNECTED) { context.applicationContext.unregisterReceiver(this) emitter.onError(WiFiException("Failed to connect to wifi network")) } } } } val networkStateChangedFilter = IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION) networkStateChangedFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION) context.applicationContext.registerReceiver(wifiConnectionReceiver, networkStateChangedFilter) emitter.setCancellable { if (!emitter.isDisposed) context.applicationContext.unregisterReceiver(wifiConnectionReceiver) } wifiManager.enableNetwork(networkId, true) } 创建的,因为它没有返回-1,所以成功了。

1 个答案:

答案 0 :(得分:0)

好吧,我终于弄清楚了,我希望我的回答对以后遇到类似问题的任何人有所启发,因为这很讨厌,使我很头疼。

问题中的代码并不完全正确,但这也不是问题的根本原因。问题的根本原因是我错误地配置了通过WiFiConfigWiFiConfig表中注册的WiFiConfigManager.addNetwork()对象。

我对WifiConfigManager.addNetwork()的合同做出了巨大的假设。我以为如果该操作成功(即未返回-1),则传递的WiFiConfig已正确配置。该假设是错误的,我创建的allowedAuthAlgorithms上的allowedProtocolsallowedKeyManagersallowedPairwiseCipherBitSet WiFiConfig是错误的,但通话addNetwork()成功。我相信这是因为对addNetwork()的调用实际上除了验证配置是否可有效地放入WiFiConfig外没有做任何事情,这与验证完全不同如果是给定WiFi接入点的正确配置。这得到了addNetwork()源代码中的注释的支持,这些注释没有像其他许多WiFiManager函数那样声明异步状态的传递,(至少对我而言)表明没有尝试调用addNetwork()导致操作系统与接入点进行通信。

由于一位同事提出了非常有帮助的建议,即通过操作系统连接到有问题的访问点,然后将操作系统为该访问点创建的WiFiConfig对象与我自己的代码生成的对象进行比较差异我注意到WiFiConfig的配置不正确。在此之后不久,我解决了原始问题。

现在,为什么我的WiFiConfig对象创建不正确?那是因为我对如何配置WiFi几乎一无所知(即各种术语以及所有协议,算法和密钥管理器背后的含义)。因此,在阅读了官方文档并且没有收集到很多有用的信息之后,我转向StackOverflow的问题和答案,并发现了一种正确设置WiFiConfig的重复模式,他们似乎都使用BitWise运算符来创建一个最终传递给IntWiFiConfig.allowedProtocols.set()WiFiConfig.allowedPairwiseCiphers.set()WiFiConfig.allowedKeyManagement.set()函数的WiFiConfig.allowedAuthAlgorithm.set()值。

事实证明,每个配置选项的基础BitSet是一个数据结构,该结构维护着位的动态调整大小矢量,其中给定BitSet实例中位的索引在WiFiConfig对象对应于String数组中元素的索引,该数组与WiFiConfig对象中的上述BitSet隐式关联。因此,如果您希望提供多个protocolskeyManagementspairwiseCiphersauthAlgorithms,则需要在基础set上调用BitSet,传递正确的索引,该索引对应于与所选协议匹配的String数组的元素。

重新编写我的WiFiConfig创建代码后,问题得以解决。尽管我的代码在原始帖子中有一个错误,该错误也已修复。

这是新的WiFiConfig创建代码:

/**
 * Emits a single of the [WifiConfiguration] created from the passed [scanResult] and [preSharedKey]
 */
private fun createWifiConfiguration(scanResult: WiFiScanResult, preSharedKey: String) = Single.fromCallable<WifiConfiguration> {
    val auth = scanResult.auth
    val keyManagement = scanResult.keyManagement
    val pairwiseCipher = scanResult.pairwiseCipher

    val config = WifiConfiguration()
    config.SSID = "\"" +  scanResult.ssid + "\""
    config.BSSID = scanResult.bssid

    if (auth.contains("WPA") || auth.contains("WPA2")) {
        config.allowedProtocols.set(WifiConfiguration.Protocol.WPA)
        config.allowedProtocols.set(WifiConfiguration.Protocol.RSN)
    }

    if (auth.contains("EAP"))
        config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.LEAP)
    else if (auth.contains("WPA") || auth.contains("WPA2"))
        config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
    else if (auth.contains("WEP"))
        config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)

    if (keyManagement.contains("IEEE802.1X"))
        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X)
    else if (auth.contains("WPA") && keyManagement.contains("EAP"))
        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP)
    else if (auth.contains("WPA") && keyManagement.contains("PSK"))
        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
    else if (auth.contains("WPA2") && keyManagement.contains("PSK"))
        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)

    if (pairwiseCipher.contains("CCMP") || pairwiseCipher.contains("TKIP")) {
        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
    }

    if (preSharedKey.isNotEmpty()) {
        if (auth.contains("WEP")) {
            if (preSharedKey.matches("\\p{XDigit}+".toRegex())) {
                config.wepKeys[0] = preSharedKey
            } else {
                config.wepKeys[0] = "\"" + preSharedKey + "\""
            }
            config.wepTxKeyIndex = 0
        } else {
            config.preSharedKey = "\"" + preSharedKey + "\""
        }
    }

    config
}

这是新的连接代码:

/**
 * Connects to the wifi access point at specified [ssid] with specified [networkId]
 * And returns the [WifiInfo] of the network that has been connected to
 */
private fun connect(context: Context,
                    wifiManager: WifiManager,
                    ssid: String,
                    networkId: Int) = Single.create<WifiInfo> { emitter ->

    val wifiConnectionReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == WifiManager.NETWORK_STATE_CHANGED_ACTION) {
                val networkInfo = intent.getParcelableExtra<NetworkInfo>(WifiManager.EXTRA_NETWORK_INFO) ?: return

                if (networkInfo.detailedState == NetworkInfo.DetailedState.CONNECTED) {
                    val wifiInfo = intent.getParcelableExtra<WifiInfo>(WifiManager.EXTRA_WIFI_INFO) ?: return
                    if (ssid.unescape() == wifiInfo.ssid.unescape()) {
                        context.applicationContext.unregisterReceiver(this)
                        emitter.onSuccess(wifiInfo)
                    }
                }
            }
        }
    }

    val networkStateChangedFilter = IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)
    networkStateChangedFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)

    context.applicationContext.registerReceiver(wifiConnectionReceiver, networkStateChangedFilter)

    emitter.setCancellable {
        if (!emitter.isDisposed)
            context.applicationContext.unregisterReceiver(wifiConnectionReceiver)
    }

    wifiManager.enableNetwork(networkId, true)
}