2016/11/30

Android で Wi-Fi Access Point に接続する


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

### コード
Wi-Fi Access Point に接続するには `WifiManager.enableNetwork()` を使用します。

既に登録済みの Access Point に接続する場合は、`WifiManager.getConfiguredNetworks()` を使って `WifiConfiguration` を取得します。

```java
`highlight: 13;
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);

// targetSsid が既に登録済みの場合
WifiConfiguration targetConfig = null;
for (WifiConfiguration config : wm.getConfiguredNetworks()) {
    if (config.SSID.equals('"' + targetSsid + '"')) { // Quote を取る方向の方が正しいかもしれないが、わかりやすさ重視で
        targetConfig = config;
        break;
    }
}
if (targetConfig != null) {
    wm.enableNetwork(targetConfig.networkId, true);
} else {
    // 登録されてなかった
}
```

まだ登録されていない Access Point の場合は `WifiManager.addNetwork()` で登録してから使用します。

> 参考
>
> [穀風: Android で Wi-Fi Access Point を登録する](http://kokufu.blogspot.jp/2016/11/android-wi-fi-access-point_26.html)

```java
`highlight: 16;
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);

// targetSsid が未登録の場合は新規登録を先に行う
// 以下は WPA の例
WifiConfiguration targetConfig = new WifiConfiguration();
targetConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
targetConfig.SSID = '"' + targetSsid + '"';
targetConfig.preSharedKey = '"' + password + '"';

// 以下を実行しても targetConfig.networkId は更新されないので
// 返り値の networkId を使う
int networkId = wm.addNetwork(targetConfig);

if (networkId != -1) {
    wm.enableNetwork(networkId, true);
} else {
    // 登録失敗
}
```


### パーミッション
`WifiManager.enableNetwork()` を実行するには `android.permission.CHANGE_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

また、 `WifiManager.getConfiguredNetworks()` を実行するには `android.permission.ACCESS_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

2016/11/26

Android で登録済みの Wi-Fi Access Point を登録解除する


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

登録してある Wi-Fi Access Point を未登録状態に戻す方法です。

Android で Wi-Fi Access Point を登録する


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

Wi-Fi Access Point を登録する方法です。

2016/11/25

Android で Wi-Fi ScanResult を WifiConfiguration に変換する方法


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

「[Android の Wi-Fi Access Point を表す ScanResult, WifiConfiguration, WifiInfo の違い](https://kokufu.blogspot.jp/2016/11/android-wi-fi-access-point-scanresult.html)」 で書いたように、接続先を追加する際には [WifiConfiguration](https://developer.android.com/reference/android/net/wifi/WifiConfiguration.html) を新規作成する必要があります。
しかし、そのような変換ユーティリティメソッドなどは用意されていません。

2016/11/24

Android の Wi-Fi Access Point を表す ScanResult, WifiConfiguration, WifiInfo の違い


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

Android の Wi-Fi Access Point を表す情報は複数あります。
具体的には以下の4種類。

- [android.net.wifi.ScanResult](https://developer.android.com/reference/android/net/wifi/ScanResult.html)
- [android.net.wifi.WifiConfiguration](https://developer.android.com/reference/android/net/wifi/WifiConfiguration.html)
- [android.net.wifi.WifiInfo](https://developer.android.com/reference/android/net/wifi/WifiInfo.html)
- [android.net.NetworkInfo](https://developer.android.com/reference/android/net/NetworkInfo.html)

これらは同じような情報を持ちつつ用途が異なるので、ここで整理しておこうと思います。
(ただ、NetworkInfo は少し特殊なので、あえてタイトルには入れませんでした。)

2016/11/17

Ubuntu で Spell Hint をCTRL + ALT + H 以外のキーに割り当てる

Ubuntu で [fcitx](https://fcitx-im.org/wiki/Fcitx) を使っていると、CTRL + ALT + H は Spell Hint の切り替えに割り当てられています。
しかし、めったに使わない機能なので、IDE 等で使用したい場合は不便です。

2016/11/11

KeePass 2 on Ubuntu でクリップボードにコピーができない

Ubuntu で [KeePass](https://sourceforge.net/projects/keepass/)Ver 2.32 を便利に使っていたのですが、
いつの日か、ユーザ名やパスワードをコピーできなくなってしまいましたCtrl + c が動作しない。

KeePass のバグかと思っていたのですが、依存するライブラリの問題だったようです。

> 参考
> 
> [KeePass / Bugs / #1057 Copy to clipboard does not work at all (Linux mono)](https://sourceforge.net/p/keepass/bugs/1057/?limit=25)


i386 系の xsel をインストールすることで解決しました。

```console
`gutter: false;
$ sudo apt-get install  xsel:i386
```

2016/11/09

Android で付近の Wi-Fi Access Point を検索して一覧を取得する


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

付近の Wi-Fi Access Point を検索する方法です。


### コード
私の所持している端末では、Wi-Fi が ON になっている時、定期的に周囲の Access Point の検索が走っているようです多分、様々なアプリが `WifiManager.startScan()` を実行しているから?。

最後に検索した結果を取得するには `WifiManager.getScanResults()` を使用します。

```java
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
List scanResults = wm.getScanResults();

for (ScanResult scanResult : scanResults) {
    Log.d(TAG, scanResult.toString());
}
```

ただ、これだと最後に取得した結果がいつのものなのかわかりません`ScanResult` に `timestamp` という情報が乗っているので、実はわかるのですが。
そこで、検索が完了した時に呼ばれる [BroadcastReceiver](https://developer.android.com/reference/android/content/BroadcastReceiver.html) を登録し、
その `onReceive()` の中で先のコードを実行することで、最新のものを取得することにします。

BroadcastReceiver ですが、
全てのアプリが受信可能な通知のため、「止める」という概念がありません。
そのため、以下のような特徴があります。

- `WifiManager.startScan()` は呼ばなくても検索が実行されていることが多い先にも書いたように、様々なアプリが `WifiManager.startScan()` を実行しているからだと思われます

- `stopScan()` という method はない

- `onReceive()` は何度も呼ばれる


止められないので、`onReceive()` が何度も呼ばれるのを防ぐには、`unregisterReceiver()` することになります。


```java
public class MainActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();

        // 検索時に呼ばれる BroadcastReceiver を登録
        registerReceiver(mScanResultsReceiver,
                new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

        // startScan() は呼ばなくても scan が裏で走っていることが多いけど念の為
        WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
        wm.startScan();
    }

    @Override
    protected void onPause() {
        super.onPause();

        try {
            unregisterReceiver(mScanResultsReceiver);
        } catch (IllegalArgumentException e) {
            // 既に登録解除されている場合
            // 事前に知るための API は用意されていない
        }
    }

    private final BroadcastReceiver mScanResultsReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // intent に情報が乗っているわけではないので、
            // WifiManager の getScanResults で結果を取得
            WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
            List scanResults = wm.getScanResults();

            for (ScanResult scanResult : scanResults) {
                Log.d(TAG, scanResult.toString());
            }

            // onReceive() は何度も呼ばれるので、
            // 1度で終了させたい場合はここで unregister する
            try {
                unregisterReceiver(this);
            } catch (IllegalArgumentException e) {
                // 既に登録解除されている場合
                // 事前に知るための API は用意されていない
            }
        }
    };
}
```

### 取得できる結果
`ScanResult` には以下のような情報が入っています。

```
SSID : my-ssid
wifiSsid : my-ssid
BSSID : aa:bb:cc:dd:ee:ff
capabilities : [WPA-PSK-CCMP][WPA2-PSK-CCMP][WPS][ESS]
frequency : 2437
level : -93
timestamp : 713047320153
```

`frequency` は Channel とほぼ同義、 `level` は RSSI とほぼ同義です。

また、API Level 23 (Marshmallow) 以降だと以下のような情報他も追加されています。
こちらは 5GHz 帯の情報になります。

```
centerFreq0 : 5610
centerFreq1 : 0 
channelWidth : 2
```

### パーミッション
`WifiManager.startScan()` を実行するには `android.permission.CHANGE_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

また、 `WifiManager.getScanResults()` を実行するには `android.permission.ACCESS_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

### Android 6.0 以降では追加のパーミッションが必要
さらに、Android 6.0 (API Level 23 Marshmallow) 以降では `android.permission.ACCESS_COARSE_LOCATION` もしくは `android.permission.ACCESS_FINE_LOCATION` が必要で、
これらは動的に許可を得ねばなりません。

> 参考
>
> [穀風: Android 6.0 以降で WifiManager.getScanResults() を普通に実行しても結果が空のリストになってしまう](https://kokufu.blogspot.jp/2016/11/android-60-wifimanagergetscanresults.html)

2016/11/08

Android 6.0 以降で WifiManager.getScanResults() を普通に実行しても結果が空のリストになってしまう

かつては `WifiManager.getScanResults()` を実行するのに必要なパーミッションは `android.permission.ACCESS_WIFI_STATE` だけでした。

しかし、そのパーミッションを記述していても、
Android 6.0 (API 23 Marshmallow) 以降で `WifiManager.getScanResults()` を実行すると **スキャン結果が0個になって** しまいます。


### 追加のパーミッションが必要になった
実は、
Android 6.0 以降では、`WifiManager.getScanResults()` を実行するのに以下の **どちらか** のパーミッションも必要になりました。
ただ、先に書いたように、このパーミッションを取得していなくても、Exception が発生するのではなく、空のリストが返ってくるため、問題に気づきにくいのが厄介な点です。

- `android.permission.ACCESS_FINE_LOCATION`
- `android.permission.ACCESS_COARSE_LOCATION`

> 参考
>
> WifiManager#getScanResults() | Android Developers

### パーミッションの取得方法
さらに、LOCATION 関連のパーミッションは AndroidManifest.xml に記述するだけでは不十分で、動的に許可を得ないといけないこれも Android 6.0 からの仕様ので注意が必要です。

以下に、`android.permission.ACCESS_COARSE_LOCATION` を取得する方法を示します。

まず、これまでと同様、AndroidManifest.xml に記述します。

```xml




```

加えて、Activity 等で動的にパーミッションを取得します。

以下に示したコードは必要最低限のものなので、具体的な実装は Android Developers も参考にしてください。

> 参考
>
> [Requesting Permissions at Run Time | Android Developers](https://developer.android.com/training/permissions/requesting.html)


```java
public class MainActivity extends Activity {
    private final int PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION = 0;

    @Override
    protected void onResume() {
        super.onResume();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 既に許可されているか確認
            if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
                // 許可されていなかったらリクエストする
                // ダイアログが表示される
                requestPermissions(
                        new String[]{
                                Manifest.permission.ACCESS_COARSE_LOCATION
                        },
                        PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION);
                return;
            }
        }

        logScanResults();
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode,
            @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 許可された場合
            logScanResults();
        } else {
            // 許可されなかった場合
            // 何らかの対処が必要
        }
    }

    private void logScanResults() {
        WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
        List scanResults = wm.getScanResults();

        for (ScanResult scanResult : scanResults) {
            Log.d(TAG, scanResult.toString());
        }
    }
}
```

上記を実行すると、
以下のようなダイアログが表示され、ユーザーの許可を求めるようになります。

2016/11/07

Android で 接続している Wi-Fi Access Point の情報を取得する


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

現在接続している Wi-Fi Access Point の情報を取得する方法です。

Access Point の情報は `WifiInfo` にまとまっているので、これを取得します。

> 参考
> 
> [穀風: Android で Wi-Fi の接続状態を確認する](http://kokufu.blogspot.jp/2016/10/android-wi-fi-access-point_27.html)


### コード
```java
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);

WifiInfo wifiInfo = wm.getConnectionInfo();

// 取得できる情報
// SSID
// BSSID
// Hidden SSID
// Ip Address
// MAC Address
// Frequency 
// RSSI
// Link Speed
// Network ID
// Supplicant State
```

接続していない場合、 `null` が返ってくる**のではなく**、`SupplicantState` が `INACTIVE` なインスタンスが返ってきます。


### パーミッション
このコードを実行するには `android.permission.ACCESS_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```