Android WiFiManager enableNetwork returning false

Tho*_*ook 7 android wifimanager android-wifi kotlin rx-java2

TO BE CLEAR:

The most likely part of the code which has the problem is the connect function, which you can find in the code block.

EDIT:

I've had a good dig through LogCat and found something interesting (this occurred the exact moment enableNetwork was called):

2018-12-04 20:13:14.508 1315-7000/? I/WifiService: enableNetwork uid=10158 disableOthers=true
2018-12-04 20:13:14.508 1315-1607/? D/WifiStateMachine: connectToUserSelectNetwork netId 49, uid 10158, forceReconnect = false
2018-12-04 20:13:14.541 1315-1607/? D/WifiConfigStore: Writing to stores completed in 14 ms.
2018-12-04 20:13:14.541 1315-1607/? E/WifiConfigManager: UID 10158 does not have permission to update configuration "SKYD7F55"WPA_PSK
2018-12-04 20:13:14.541 1315-1607/? I/WifiStateMachine: connectToUserSelectNetwork Allowing uid 10158 with insufficient permissions to connect=49
Run Code Online (Sandbox Code Playgroud)

PERMISSIONS:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Run Code Online (Sandbox Code Playgroud)

I've been tasked to create a part of an app where the user will be able to see a list of scanned WiFi access points (ScanResult), be able to select one, and, if it requires auth, be presented with a screen where they enter the PSK. After entering the PSK, the system will attempt to connect to the access point by first creating and configuring a WifiConfig object, using addNetwork to add the config to the Wifi config table, then disconnect, enableNetwork and reconnect (in that order).

I'm using RX-Java2 so that I can chain the various steps of the network setup. For instance, the disconnect method returns a Completable which emits a completed event if WifiManager.disconnect() succeeds. It does so by registering a BroadcastReceiver to listen for NETWORK_STATE_CHANGED_ACTION and then emitting a completion event if the networkInfo extra has detailed state DISCONNECTED. The same logic applies for the connect() function.

Now, addNetwork() is succeeding (so my WiFi config functions are correct), and then I am chaining disconnect Completable to the connect Single using andThen. I have put breakpoints in my code and can see that everything is running in the correct order, disconnect is succeeding and then has it's broadcast receiver registered successfully, but enableNetwork() call is returning false (indicating that the enableNetwork command failed to be issued by the OS).

I am 99% sure this is not an issue with how I'm using RX, but, given that addNetwork and disconnect are succeeding (indicating my wifi config creation code is fine) I am starting to wonder if either A) My RX code is wrong, or, B) My WiFi config creation is wrong.

因此,我将发布以下所有代码以及用例,并非常感谢任何建议.

Emitters.kt:

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.NetworkInfo
import android.net.wifi.SupplicantState
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import com.google.common.base.Optional
import io.reactivex.*
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.lang.Exception

private const val TAG = "Emitters"
private const val SIGNAL_STRENGTH_RANGE = 4

/**
 * Use case of these emitters (in presenter or interactor):
 *
 * // If network is open, then go ahead and connect to it, else show enter password form
 * isNetworkOpen(context, scanResult).flatMapCompletable { isNetworkOpen ->
 *     if (isNetworkOpen) {
 *         connectToOpenWifi(context, scanResult.ssid, scanResult.bssid)
 *     } else {
 *         Completable.error(WifiException("The specified network requires a password")
 *     }
 * }.subscribeOn(Schedulers.io())
 *  .observeOn(AndroidSchedulers.mainThread())
 *  .subscribe({
 *      view?.showSuccessfullyConnected()
 *  }, { error ->
 *         when (error) {
 *             is WifiAuthException -> {
 *                  val auth = error.wifiScanResult.auth
 *                  val keyManagement = error.wifiScanResult.keyManagement
 *                  val security = "$auth/$keyManagement"
 *                  viewStateStack.add(NetworkSummaryViewState(error.wifiScanResult, security))
 *                  switchToViewState(viewStateStack.peek())
 *              } else -> {
 *                  viewStateStack.add(FailedToConnectViewState(networkName))
 *                  switchToViewState(viewStateStack.peek())
 *              }
 *          }
 *      }
 *  })
 *
 *  // Called by view to connect to closed network with provided password
 *  connectToClosedWifi(context, scanResult, password)
 *  .subscribeOn(Schedulers.io())
 *  .observeOn(AndroidSchedulers.mainThread())
 *  .subscribe({
 *      view?.showSuccessfullyConnected()
 *  }, { error ->
 *      view?.showFailedToConnect()
 *  })
 */

/**
 * Creates a Flowable that emits WiFiScanResults
 */
fun wifiScanResults(context: Context): Flowable<Set<WiFiScanResult>> = Flowable.create<Set<WiFiScanResult>> ({ emitter ->
    val wifiManagerWrapper = WifiManagerWrapper(context.applicationContext)

    if (!wifiManagerWrapper.wifiManager.isWifiEnabled && !wifiManagerWrapper.wifiManager.setWifiEnabled(true)) {
        wifiManagerWrapper.dispose()
        emitter.onError(WiFiException("WiFi not enabled and couldn't enable it"))
        return@create
    }

    // Broadcast receiver that handles wifi scan results
    val wifiScanReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) {
                val scanResults = wifiManagerWrapper.wifiManager.scanResults

                if (scanResults !== null) {
                    emitter.onNext(scanResults.map { scanResult ->
                        val signalStrength = WifiManager.calculateSignalLevel(scanResult.level, SIGNAL_STRENGTH_RANGE)

                        val capabilities = scanResult.capabilities.substring(1, scanResult.capabilities.indexOf(']') -1)
                            .split('-')
                            .toSet()

                        WiFiScanResult(scanResult.SSID,
                            scanResult.BSSID,
                            capabilities.elementAtOrNull(0) ?: "",
                            capabilities.elementAtOrNull(1) ?: "",
                            capabilities.elementAtOrNull(2) ?: "",
                            signalStrength)

                    }.toSet())
                }
            }

            if (!wifiManagerWrapper.wifiManager.startScan()) {
                emitter.onError(WiFiException("WiFi not enabled"))
            }
        }
    }

    val wifiScanResultsIntentFilter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
    context.applicationContext.registerReceiver(wifiScanReceiver, wifiScanResultsIntentFilter)

    emitter.setCancellable {
        context.unregisterReceiver(wifiScanReceiver)
        wifiManagerWrapper.dispose()
    }

    if (!wifiManagerWrapper.wifiManager.startScan()) {
        emitter.onError(WiFiException("WiFi not enabled"))
    }
}, BackpressureStrategy.LATEST).subscribeOn(Schedulers.io())

/**
 * Returns a single indicating if the [scanResult] is open
 */
fun isNetworkOpen(context: Context,
                  scanResult: WiFiScanResult): Single<Boolean> = Single.create<Boolean> { emitter ->
    val wifiManagerWrapper = WifiManagerWrapper(context.applicationContext)

    emitter.setCancellable {
        wifiManagerWrapper.dispose()
    }

    if (scanResult.auth.contains("WEP")) {
        emitter.onSuccess(true)
    } else {
        emitter.onSuccess(false)
    }
}

/**
 * Attempts to connect to an open wifi access point specified by [scanResult]
 * Emits a completed event if successful, else emits an error
 */
fun connectToOpenWifi(context: Context,
                      scanResult: WiFiScanResult): Completable = Completable.create { emitter ->
    val ssid = scanResult.ssid
    val bssid = scanResult.bssid

    val wifiManagerWrappper = WifiManagerWrapper(context.applicationContext)

    if (!wifiManagerWrappper.wifiManager.isWifiEnabled && !wifiManagerWrappper.wifiManager.setWifiEnabled(true)) {
        wifiManagerWrappper.dispose()
        emitter.onError(WiFiException("Wifi not enabled"))
    }

    val updateWifiStateObs = getExistingConfiguration(wifiManagerWrappper.wifiManager, ssid, bssid).flatMap { existingConfig ->
        if (!existingConfig.isPresent) {
            createOpenWifiConfiguration(scanResult).flatMap { wifiConfig ->
                val newNetworkId = wifiManagerWrappper.wifiManager.addNetwork(wifiConfig)
                if (newNetworkId < 0)
                    throw WiFiException("Failed to add new access point ${scanResult.ssid}")

                val currentWifiConnection = wifiManagerWrappper.wifiManager.connectionInfo

                if (currentWifiConnection !== null) {
                    disconnect(context, wifiManagerWrappper.wifiManager).andThen(
                        connect(context, wifiManagerWrappper.wifiManager, wifiConfig.SSID, newNetworkId)
                    )
                } else {
                    connect(context, wifiManagerWrappper.wifiManager, wifiConfig.SSID, newNetworkId)
                }
            }
        } else {
            Single.just(existingConfig.get())
        }
    }

    val compositeDisposable = CompositeDisposable()

    emitter.setCancellable {
        compositeDisposable.clear()
        wifiManagerWrappper.dispose()
    }

    try {
        compositeDisposable.add(updateWifiStateObs.subscribe({
            emitter.onComplete()
        }, { error ->
            emitter.onError(error)
        }))
    } catch (ex: Exception) {
        compositeDisposable.clear()
        wifiManagerWrappper.dispose()
        emitter.onError(ex)
    }
}

/**
 * Attempts to connect to an closed [scanResult] by providing the given [preSharedKey]
 * Emits a completed event if successful, else emits an error
 */
fun connectToClosedWifi(context: Context,
                        scanResult: WiFiScanResult,
                        preSharedKey: String): Completable = Completable.create { emitter ->
    val ssid = scanResult.ssid
    val bssid = scanResult.bssid

    val wifiManagerWrappper = WifiManagerWrapper(context.applicationContext)

    if (!wifiManagerWrappper.wifiManager.isWifiEnabled && !wifiManagerWrappper.wifiManager.setWifiEnabled(true)) {
        wifiManagerWrappper.dispose()
        emitter.onError(WiFiException("Wifi not enabled"))
    }

    val updateWifiStateObs =
        getExistingConfiguration(wifiManagerWrappper.wifiManager, ssid, bssid).flatMap { existingConfig ->
            if (!existingConfig.isPresent) {
                createClosedWifiConfiguaration(scanResult, preSharedKey).flatMap { wifiConfig ->
                    val newNetworkId = wifiManagerWrappper.wifiManager.addNetwork(wifiConfig)
                    if (newNetworkId < 0)
                        throw WiFiException("Failed to add new access point ${scanResult.ssid}")

                    val currentWifiConnection = wifiManagerWrappper.wifiManager.connectionInfo

                    if (currentWifiConnection !== null) {
                        disconnect(context, wifiManagerWrappper.wifiManager).andThen(
                            connect(context, wifiManagerWrappper.wifiManager, wifiConfig.SSID, newNetworkId)
                        )
                    } else {
                        connect(context, wifiManagerWrappper.wifiManager, wifiConfig.SSID, newNetworkId)
                    }
                }
            } else {
                Single.just(existingConfig.get())
            }
        }

    val compositeDisposable = CompositeDisposable()

    emitter.setCancellable {
        compositeDisposable.clear()
        wifiManagerWrappper.dispose()
    }

    try {
        compositeDisposable.add(updateWifiStateObs.subscribe({
            emitter.onComplete()
        }, { error ->
            emitter.onError(error)
        }))
    } catch (ex: Exception) {
        compositeDisposable.clear()
        wifiManagerWrappper.dispose()
        emitter.onError(ex)
    }
}

/**
 * Wrapper class for WiFiManager that creates a multicast lock to make the app handle multicast wifi packets
 * Handles disposing of the lock and cleaning up of resources via the dispose method
 */
private class WifiManagerWrapper(context: Context) {
    val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE)
            as? WifiManager ?: throw IllegalStateException("Could not get system Context.WIFI_SERVICE")

    // Create and acquire a multicast lock to start receiving multicast wifi packets
    private var lock = wifiManager.createMulticastLock(TAG + "_lock").apply {
        acquire()
    }

    // Dispose of the lock
    fun dispose() {
        if (lock.isHeld) {
            try {
                lock.release()
            } catch (ignore: Exception) {
                EventReporter.i(TAG, "Failed to release lock on wifi manager wrapper")
            }
            lock = null
        }
    }
}

/**
 * Disconnects from the connected wifi network and emits a completed event if no errors occurred
 */
private fun disconnect(context: Context,
                       wifiManager: WifiManager) = Completable.create { emitter ->

    val wifiDisconnectionReceiver = 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.DISCONNECTED) {
                    context.applicationContext.unregisterReceiver(this)
                    emitter.onComplete()
                }
            }
        }
    }

    val networkStateChangedFilter = IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)

    context.applicationContext.registerReceiver(wifiDisconnectionReceiver, networkStateChangedFilter)

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

    if (!wifiManager.disconnect())
        emitter.onError(WiFiException("Failed to issue disconnect command to wifi subsystem"))
}

/**
 * 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)
    wifiManager.reconnect()
}

/**
 * Returns a Single, wrapping an Optional.absent if no existing configuration exists with the passed [ssid] and [bssid], else the found [WifiConfiguration]
  */
private fun getExistingConfiguration(wifiManager: WifiManager,
                                     ssid: String,
                                     bssid: String) = Single.create<Optional<WifiConfiguration>> { emitter ->
    val configuredNetworks = wifiManager.configuredNetworks
    if (configuredNetworks.isEmpty()) {
        emitter.onSuccess(Optional.absent())
    }
    emitter.onSuccess(Optional.fromNullable(configuredNetworks.firstOrNull { configuredNetwork ->
        configuredNetwork.SSID.unescape() == ssid && configuredNetwork.BSSID == bssid
    }))
}

/**
 * Emits a single of the open [WifiConfiguration] created from the passed [scanResult]
 */
private fun createOpenWifiConfiguration(scanResult: WiFiScanResult) = 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

    var allowedProtocols = 0
    when {
        auth.isEmpty() -> {
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.RSN
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.WPA
        }
        auth.contains("WPA2") -> allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.RSN
        auth.contains("WPA") -> {
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.WPA
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.RSN
        }
    }

    config.allowedProtocols.set(allowedProtocols)

    var allowedAuthAlgos = 0
    when {
        auth.contains("EAP") -> allowedAuthAlgos = allowedAuthAlgos or WifiConfiguration.AuthAlgorithm.LEAP
        auth.contains("WPA") -> allowedAuthAlgos = allowedAuthAlgos or WifiConfiguration.AuthAlgorithm.OPEN
        auth.contains("WEP") -> allowedAuthAlgos = allowedAuthAlgos or WifiConfiguration.AuthAlgorithm.SHARED
    }

    config.allowedAuthAlgorithms.set(allowedAuthAlgos)

    var allowedKeyManagers = WifiConfiguration.KeyMgmt.NONE
    if (keyManagement.contains("IEEE802.1X"))
        allowedKeyManagers = allowedKeyManagers or WifiConfiguration.KeyMgmt.IEEE8021X
    else if (auth.contains("WPA") && keyManagement.contains("EAP"))
        allowedKeyManagers = allowedKeyManagers or WifiConfiguration.KeyMgmt.WPA_EAP
    else if (auth.contains("WPA") && keyManagement.contains("PSK"))
        allowedKeyManagers = allowedKeyManagers or WifiConfiguration.KeyMgmt.WPA_PSK

    config.allowedKeyManagement.set(allowedKeyManagers)

    var allowedPairWiseCiphers = WifiConfiguration.PairwiseCipher.NONE
    if (pairwiseCipher.contains("CCMP"))
        allowedPairWiseCiphers = allowedPairWiseCiphers or WifiConfiguration.PairwiseCipher.CCMP
    if (pairwiseCipher.contains("TKIP"))
        allowedPairWiseCiphers = allowedPairWiseCiphers or WifiConfiguration.PairwiseCipher.TKIP

    config.allowedPairwiseCiphers.set(allowedPairWiseCiphers)

    config
}

/**
 * Emits a single of the closed [WifiConfiguration] created from the passed [scanResult] and [preSharedKey]
 * Or, emits an error signalling the [preSharedKey] was empty
 */
private fun createClosedWifiConfiguaration(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

    var allowedProtocols = 0
    when {
        auth.isEmpty() -> {
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.RSN
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.WPA
        }
        auth.contains("WPA2") -> allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.RSN
        auth.contains("WPA") -> {
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.WPA
            allowedProtocols = allowedProtocols or WifiConfiguration.Protocol.RSN
        }
    }

    config.allowedProtocols.set(allowedProtocols)

    var allowedAuthAlgos = 0
    when {
        auth.contains("EAP") -> allowedAuthAlgos = allowedAuthAlgos or WifiConfiguration.AuthAlgorithm.LEAP
        auth.contains("WPA") || auth.contains("WPA2") -> allowedAuthAlgos = allowedAuthAlgos or WifiConfiguration.AuthAlgorithm.OPEN
        auth.contains("WEP") -> allowedAuthAlgos = allowedAuthAlgos or WifiConfiguration.AuthAlgorithm.SHARED
    }

    config.allowedAuthAlgorithms.set(allowedAuthAlgos)

    var allowedKeyManagers = WifiConfiguration.KeyMgmt.NONE
    if (keyManagement.contains("IEEE802.1X"))
        allowedKeyManagers = allowedKeyManagers or WifiConfiguration.KeyMgmt.IEEE8021X
    else if (auth.contains("WPA") && keyManagement.contains("EAP"))
        allowedKeyManagers = allowedKeyManagers or WifiConfiguration.KeyMgmt.WPA_EAP
    else if (auth.contains("WPA") && keyManagement.contains("PSK"))
        allowedKeyManagers = allowedKeyManagers or WifiConfiguration.KeyMgmt.WPA_PSK
    else if (preSharedKey.isNotEmpty())
        allowedKeyManagers = allowedKeyManagers or WifiConfiguration.KeyMgmt.WPA_PSK
    else if (preSharedKey.isEmpty())
        allowedKeyManagers = allowedAuthAlgos or WifiConfiguration.KeyMgmt.WPA_PSK

    config.allowedKeyManagement.set(allowedKeyManagers)

    var allowedPairWiseCiphers = WifiConfiguration.PairwiseCipher.NONE
    if (pairwiseCipher.contains("CCMP"))
        allowedPairWiseCiphers = allowedPairWiseCiphers or WifiConfiguration.PairwiseCipher.CCMP
    if (pairwiseCipher.contains("TKIP"))
        allowedPairWiseCiphers = allowedPairWiseCiphers or WifiConfiguration.PairwiseCipher.TKIP

    config.allowedPairwiseCiphers.set(allowedPairWiseCiphers)

    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
}

/**
 * Extension function to remove escaped " from a string
 */
private fun String.unescape() =
    if (this.startsWith("\""))
        this.replace("\"", "")
    else
        this
Run Code Online (Sandbox Code Playgroud)

Tho*_*ook 7

好吧,我终于弄明白这一点了,我希望我的答案能为未来遇到类似问题的人带来一些启示,因为这是令人讨厌的,让我头痛不已.

问题的根本原因是我错误地配置了通过表WiFiConfig中注册的对象.WiFiConfigWiFiConfigManager.addNetwork()

我曾就合同做出了大量假设WifiConfigManager.addNetwork().我假设如果该操作成功(即没有返回-1),则WiFiConfig正确配置传递.这种假设是不正确的allowedAuthAlgorithms,allowedProtocols,allowedKeyManagersallowedPairwiseCipher BitSetWiFiConfig我创建是不正确的,但调用addNetwork()成功.我相信这是因为addNetwork()除了验证配置是否有效放入WiFiConfig表之外,调用实际上并没有做任何事情,这与验证它是否是给定WiFi接入点的正确配置完全不同.这由源代码中的注释支持,addNetwork()其中没有像许多其他WiFiManager函数那样声明异步状态的传递,指示(至少对我来说)操作系统没有尝试与接入点通信因为打电话addNetwork().

由于同事通过操作系统连接到相关接入点的非常有用的建议,然后WiFiConfig将该接入点的操作系统创建对象与我自己的代码生成的对象进行比较以发现差异,我注意到我WiFiConfig正在配置不正确.在此之后不久,我解决了原来的问题.

现在,为什么我的WiFiConfig对象被错误地创建了?那是因为我对如何配置WiFi知之甚少(即各种术语和所有协议,算法和密钥管理器背后的含义).所以,在阅读官方文档,而不是拾遗很多有用的信息后,我转向StackOverflow的问题和答案,并找到了设置重复型式WiFiConfig正确时,他们都显得使用BitWise运营商创建Int这是最终传递给值WiFiConfig.allowedProtocols.set(),WiFiConfig.allowedPairwiseCiphers.set(),WiFiConfig.allowedKeyManagement.set()WiFiConfig.allowedAuthAlgorithm.set()功能.

事实证明,底层BitSet为每个这些配置选项是其保持的比特的动态调整向量,其中在给定的比特的索引的数据结构BitSet在WiFiConfig对象实例隐含对应于一个元素的索引中的String WiFiConfig对象中的关联数组.因此,如果希望提供多个protocols,keyManagements,pairwiseCiphersauthAlgorithms你需要调用set于底层相应BitSet,传递,其中将对应于所述的元件的正确索引隐式地,其匹配所选择的协议相关联的字符串数组.

重写我的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
}
Run Code Online (Sandbox Code Playgroud)

这是新的连接代码:

/**
 * 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)
}
Run Code Online (Sandbox Code Playgroud)