Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android WiFiManager enableNetwork returning false

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

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

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.

Therefore, I am going to post all the code below as well as the use case and would greatly appreciate any advice.

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
like image 376
Thomas Cook Avatar asked Dec 04 '18 19:12

Thomas Cook


1 Answers

Ok, I've finally figured this out and I hope that my answer here sheds some light for anyone in the future who encounters a similar problem, because this was nasty and caused me quite the headache.

The root cause of the issue was that I had incorrectly configure the WiFiConfig object which was registered in the WiFiConfig table via WiFiConfigManager.addNetwork().

I had made a massive assumption about the contract of WifiConfigManager.addNetwork(). I had assumed that if that operation succeeded (i.e. did NOT return -1) then the passed WiFiConfig was configured correctly. This assumption is incorrect, the allowedAuthAlgorithms, allowedProtocols, allowedKeyManagers and allowedPairwiseCipher BitSet on the WiFiConfig I was creating were incorrect, yet the call to addNetwork() succeeded. I believe this is because the call to addNetwork() does not actually do anything other than validate that the config is valid to put in the WiFiConfig table, which is quite different than validating if it is the correct config for a given WiFi access point. This is backed up by the comments in the source code for addNetwork() which do NOT state the delivery of asynchronous state like a lot of the other WiFiManager functions, indicating (to me at least) that no attempt to communicate with the access point was made by the OS as a result of calling addNetwork().

Due to a very helpful suggestion by a colleague to connect to the access point in question via the OS, and then to compare the OS created WiFiConfig object for that access point with the one generated by my own code for discrepancies I noticed that my WiFiConfig was being configured incorrectly. It was shortly after this that I resolved the original question.

Now, why was my WiFiConfig object being created incorrectly? That is because I had little knowledge of how to configure WiFi (i.e. the various terminology and the meaning behind all the protocols, algorithms and key managers). So, after reading the official docs and not gleaning much helpful information I turned to StackOverflow questions and answers and found a recurring pattern for setting the WiFiConfig up correctly, they all appeared to use BitWise operators to create an Int value which was ultimately passed to the WiFiConfig.allowedProtocols.set(), WiFiConfig.allowedPairwiseCiphers.set(), WiFiConfig.allowedKeyManagement.set() and WiFiConfig.allowedAuthAlgorithm.set() functions.

It turns out that the underlying BitSet for each of those configuration options is a data structure which maintains a dynamically resizing vector of bits, where the index of a bit in a given BitSet instance in the WiFiConfig object implicitly corresponded to the index of an element in an implicitly associated String array within the WiFiConfig object. Therefore, if you wished to provide multiple protocols, keyManagements, pairwiseCiphers or authAlgorithms you would need to call set on the underlying corresponding BitSet, passing in the correct index which would correspond to the element of the implicitly associated String array which matched the chosen protocol.

After re-writing my WiFiConfig creation code, the issue resolved itself. Although there was a bug in my code in the original post which has also been fixed.

Here is the new WiFiConfig creation code:

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

And here is the new connect code:

/**
 * 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)
}
like image 104
Thomas Cook Avatar answered Sep 27 '22 19:09

Thomas Cook