Android WiFiManager enableNetwork 返回 false
Android WiFiManager enableNetwork returning false
要清楚:
代码中最有可能出现问题的部分是连接函数,您可以在代码块中找到它。
编辑:
我仔细研究了 LogCat 并发现了一些有趣的东西(这发生在调用 enableNetwork 的那一刻):
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
权限:
<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" />
我的任务是创建一个应用程序的一部分,用户可以在其中看到扫描的 WiFi 接入点列表 (ScanResult
),并且能够 select 一个,并且,如果它需要身份验证,则会显示一个屏幕,让他们输入 PSK
。输入PSK
后,系统会尝试连接接入点,首先创建并配置一个WifiConfig
对象,使用addNetwork
将配置添加到Wifi配置table,然后是 disconnect
、enableNetwork
和 reconnect
(按此顺序)。
我正在使用 RX-Java2
以便我可以链接网络设置的各个步骤。例如,disconnect
方法 returns a Completable
如果 WifiManager.disconnect()
成功,它会发出一个完成的事件。它通过注册一个 BroadcastReceiver
来侦听 NETWORK_STATE_CHANGED_ACTION
然后在 networkInfo extra 具有详细状态 DISCONNECTED
时发出完成事件来实现。同样的逻辑适用于 connect()
函数。
现在,addNetwork()
成功了(所以我的 WiFi 配置函数是正确的),然后我使用 andThen
将断开连接 Completable
链接到连接 Single
。我在我的代码中放置了断点,可以看到一切都是 运行 的正确顺序,断开连接成功,然后它的广播接收器注册成功,但是 enableNetwork()
调用返回 false(表明 enableNetwork
命令未能由 OS) 发出。
我 99% 确定这不是我使用 RX 的方式的问题,但是,考虑到 addNetwork
和 disconnect
成功(表明我的 wifi 配置创建代码没问题)我开始怀疑是不是 A) 我的 RX 代码有误,或者 B) 我的 WiFi 配置创建有误。
因此,我将 post 下面的所有代码以及用例,非常感谢任何建议。
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
好的,我终于弄明白了,我希望我在这里的回答能为将来遇到类似问题的任何人提供一些启示,因为这很讨厌,让我很头疼。
问题的根本原因是我错误地配置了通过 WiFiConfigManager.addNetwork()
.[=50= 在 WiFiConfig
table 中注册的 WiFiConfig
对象]
我对 WifiConfigManager.addNetwork()
的合同做出了巨大的假设。我曾假设如果该操作成功(即未 return -1
),则传递的 WiFiConfig
已正确配置。这个假设是不正确的,我创建的 WiFiConfig
上的 allowedAuthAlgorithms
、allowedProtocols
、allowedKeyManagers
和 allowedPairwiseCipher
BitSet
是不正确的,但是调用至 addNetwork()
成功。我相信这是因为调用 addNetwork()
除了验证配置 是否有效以放入 WiFiConfig
table 之外实际上没有做任何事情,这与验证它是否 是给定 WiFi 接入点 的正确配置完全不同。 addNetwork()
的源代码中的注释支持这一点,它不像许多其他 WiFiManager
函数那样声明异步状态的传递,表明(至少对我而言)没有尝试由于调用 addNetwork()
.
,OS 与接入点进行了通信
由于同事的一个非常有用的建议,通过 OS 连接到有问题的接入点,然后比较 OS 为该访问创建的 WiFiConfig
对象指向由我自己的代码生成的差异我注意到我的 WiFiConfig
配置不正确。不久之后,我解决了原来的问题。
现在,为什么我的 WiFiConfig
对象创建不正确?那是因为我对如何配置 WiFi 知之甚少(即各种术语以及所有协议、算法和密钥管理器背后的含义)。因此,在阅读了官方文档并没有收集到太多有用的信息后,我转向 Whosebug 问题和答案并找到了正确设置 WiFiConfig
的重复模式,它们似乎都使用 BitWise
运算符来创建一个Int
值最终传递给 WiFiConfig.allowedProtocols.set()
、WiFiConfig.allowedPairwiseCiphers.set()
、WiFiConfig.allowedKeyManagement.set()
和 WiFiConfig.allowedAuthAlgorithm.set()
函数。
事实证明,每个配置选项的基础 BitSet
是一个数据结构,它维护一个动态调整大小的位向量,其中给定 BitSet
实例中的位索引在 WiFiConfig 对象中 implicitly 对应于 WiFiConfig 对象中 implicitly 关联的 String
数组中元素的索引。因此,如果您希望提供多个 protocols
、keyManagements
、pairwiseCiphers
或 authAlgorithms
,您需要在底层相应的 BitSet
上调用 set
,传入正确的索引,该索引对应于 隐式 关联的字符串数组的元素,该数组与所选协议相匹配。
重写我的 WiFiConfig
创建代码后,问题自行解决。虽然我的原始代码中有一个错误 post 也已修复。
这是新的 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)
}
要清楚:
代码中最有可能出现问题的部分是连接函数,您可以在代码块中找到它。
编辑:
我仔细研究了 LogCat 并发现了一些有趣的东西(这发生在调用 enableNetwork 的那一刻):
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
权限:
<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" />
我的任务是创建一个应用程序的一部分,用户可以在其中看到扫描的 WiFi 接入点列表 (ScanResult
),并且能够 select 一个,并且,如果它需要身份验证,则会显示一个屏幕,让他们输入 PSK
。输入PSK
后,系统会尝试连接接入点,首先创建并配置一个WifiConfig
对象,使用addNetwork
将配置添加到Wifi配置table,然后是 disconnect
、enableNetwork
和 reconnect
(按此顺序)。
我正在使用 RX-Java2
以便我可以链接网络设置的各个步骤。例如,disconnect
方法 returns a Completable
如果 WifiManager.disconnect()
成功,它会发出一个完成的事件。它通过注册一个 BroadcastReceiver
来侦听 NETWORK_STATE_CHANGED_ACTION
然后在 networkInfo extra 具有详细状态 DISCONNECTED
时发出完成事件来实现。同样的逻辑适用于 connect()
函数。
现在,addNetwork()
成功了(所以我的 WiFi 配置函数是正确的),然后我使用 andThen
将断开连接 Completable
链接到连接 Single
。我在我的代码中放置了断点,可以看到一切都是 运行 的正确顺序,断开连接成功,然后它的广播接收器注册成功,但是 enableNetwork()
调用返回 false(表明 enableNetwork
命令未能由 OS) 发出。
我 99% 确定这不是我使用 RX 的方式的问题,但是,考虑到 addNetwork
和 disconnect
成功(表明我的 wifi 配置创建代码没问题)我开始怀疑是不是 A) 我的 RX 代码有误,或者 B) 我的 WiFi 配置创建有误。
因此,我将 post 下面的所有代码以及用例,非常感谢任何建议。
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
好的,我终于弄明白了,我希望我在这里的回答能为将来遇到类似问题的任何人提供一些启示,因为这很讨厌,让我很头疼。
问题的根本原因是我错误地配置了通过 WiFiConfigManager.addNetwork()
.[=50= 在 WiFiConfig
table 中注册的 WiFiConfig
对象]
我对 WifiConfigManager.addNetwork()
的合同做出了巨大的假设。我曾假设如果该操作成功(即未 return -1
),则传递的 WiFiConfig
已正确配置。这个假设是不正确的,我创建的 WiFiConfig
上的 allowedAuthAlgorithms
、allowedProtocols
、allowedKeyManagers
和 allowedPairwiseCipher
BitSet
是不正确的,但是调用至 addNetwork()
成功。我相信这是因为调用 addNetwork()
除了验证配置 是否有效以放入 WiFiConfig
table 之外实际上没有做任何事情,这与验证它是否 是给定 WiFi 接入点 的正确配置完全不同。 addNetwork()
的源代码中的注释支持这一点,它不像许多其他 WiFiManager
函数那样声明异步状态的传递,表明(至少对我而言)没有尝试由于调用 addNetwork()
.
由于同事的一个非常有用的建议,通过 OS 连接到有问题的接入点,然后比较 OS 为该访问创建的 WiFiConfig
对象指向由我自己的代码生成的差异我注意到我的 WiFiConfig
配置不正确。不久之后,我解决了原来的问题。
现在,为什么我的 WiFiConfig
对象创建不正确?那是因为我对如何配置 WiFi 知之甚少(即各种术语以及所有协议、算法和密钥管理器背后的含义)。因此,在阅读了官方文档并没有收集到太多有用的信息后,我转向 Whosebug 问题和答案并找到了正确设置 WiFiConfig
的重复模式,它们似乎都使用 BitWise
运算符来创建一个Int
值最终传递给 WiFiConfig.allowedProtocols.set()
、WiFiConfig.allowedPairwiseCiphers.set()
、WiFiConfig.allowedKeyManagement.set()
和 WiFiConfig.allowedAuthAlgorithm.set()
函数。
事实证明,每个配置选项的基础 BitSet
是一个数据结构,它维护一个动态调整大小的位向量,其中给定 BitSet
实例中的位索引在 WiFiConfig 对象中 implicitly 对应于 WiFiConfig 对象中 implicitly 关联的 String
数组中元素的索引。因此,如果您希望提供多个 protocols
、keyManagements
、pairwiseCiphers
或 authAlgorithms
,您需要在底层相应的 BitSet
上调用 set
,传入正确的索引,该索引对应于 隐式 关联的字符串数组的元素,该数组与所选协议相匹配。
重写我的 WiFiConfig
创建代码后,问题自行解决。虽然我的原始代码中有一个错误 post 也已修复。
这是新的 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)
}