Android の Wi-Fi まわりのことメモ

WiFi まわりのことを扱う機会があったので、復習がてら整理してみる。

# パーミッション取得

- AndroidManifest.xml に追加

OS、 targetSDK によって必要な権限が異なる。詳しくは[公式ドキュメント](https://developer.android.com/guide/topics/connectivity/wifi-scan?hl=ja#wifi-scan-permissions)参照。
以下は一例。

```xml:AndroidManifest.xml
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
```

- 権限取得処理実装

必要なタイミングで権限をユーザーに求める処理を呼び出す。

```kotlin:activity
private val PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION = 0

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    なんかボタン.setOnClickListener {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION)
        } else {
            // 許可済みの処理
        }
    }
}

override fun onRequestPermissionsResult(requestCode: Int, @NonNull permissions: Array<String>, @NonNull grantResults: IntArray) {
    if (requestCode != PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION) {
        return
    }
    if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // 許可済みの処理
    }
}
```

# WiFi をスキャンする

```kotlin:activity
    val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager

    val wifiScanReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
            if (success) {
                scanSuccess()
            } else {
                scanFailure()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // startScan() が呼ばれると SCAN_RESULTS_AVAILABLE_ACTION がブロードキャストされる
        registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))

        val success = wifiManager.startScan()
        if (!success) {
            scanFailure()
        }
    }

    private fun scanSuccess() {
        // スキャン成功処理
    }

    private fun scanFailure() {
        // スキャン失敗処理
    }
```

# WiFi に接続する

```kotlin
なんかボタン.setOnClickListener {

    val connectScanResult: ScanResult = 接続したいWiFiの ScanResult

    // 既に保存されている WiFi の設定情報を取得
    var configuredNetworks: List<WifiConfiguration>?
    try {
        configuredNetworks = wifiManager.configuredNetworks
    } catch (e: Exception) {
        configuredNetworks = null
    }

    var networkId = -1
    if (configuredNetworks != null) {
        for (configuredNetwork: WifiConfiguration in configuredNetworks) {
            // "（ダブルクォーテーション） が前後についているので除外している
            val normalizedSsid = configuredNetwork.SSID.substring(1, configuredNetwork.SSID.length - 1)
            
            // すでに接続情報があるかチェックする
            if (connectScanResult.SSID.equals(normalizedSsid)) {
                networkId = configuredNetwork.networkId
                break
            }
        }
    }
    if (networkId < 0) {
        // 接続情報がないので作る
        val configuration = WifiConfiguration().apply {
            SSID = "\"$connectScanResult.SSID\""

            // 設定する前に一回クリアする（よくわかってない・・・）
            allowedProtocols.clear()
            allowedProtocols.set(WifiConfiguration.Protocol.RSN)
            allowedProtocols.set(WifiConfiguration.Protocol.WPA)

            allowedAuthAlgorithms.clear()

            allowedPairwiseCiphers.clear()
            allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
            allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)

            allowedGroupCiphers.clear()
            allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
            allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)

            // 設定する
            // connectScanResult.capabilities に "WEP"、"PSK" などが含まれるかどうかで設定方法が変わる
            allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)

            allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
            allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)

            allowedGroupCiphers.clear()
            allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40)
            allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104)

            wepKeys[0] = "\"接続する WiFi のパスワード\""
            wepTxKeyIndex = 0
        }
        networkId = wifiManager.addNetwork(configuration)
    }
    if (networkId < 0) {
        Toast.makeText(this, "接続できませんでした", Toast.LENGTH_SHORT).show()
        return
    }
    if (wifiManager.enableNetwork(networkId, true)) {
        Toast.makeText(this, connectScanResult.SSID + "に接続しました。", Toast.LENGTH_SHORT).show()
    } else {
        Toast.makeText(this, "接続できませんでした", Toast.LENGTH_SHORT).show()
    }
}
```

# 接続中のネットワークの SSID を取得する

```kotlin
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager

if (wifiManager.connectionInfo.networkId != -1) {
    // ネットワーク接続中
    // ※ ここで取得できる SSID は先頭と末尾に "（ダブルクォーテーション） が前後についている
    // https://developer.android.com/reference/android/net/wifi/WifiInfo#getSSID()
    val ssid = wifiManager.connectionInfo.ssid
} else {
    // ネットワークに接続されていない
}
```

# 参考

- [公式ドキュメント](https://developer.android.com/guide/topics/connectivity/wifi-scan?hl=ja)