2016/11/30

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

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

コード

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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 を登録する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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 で設定する必要があります。

1
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

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

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
2016/11/26

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

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

2016/11/25

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

2016/11/24

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

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

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

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

2016/11/17

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

Ubuntu で fcitx を使っていると、CTRL + ALT + H は Spell Hint の切り替えに割り当てられています。 しかし、めったに使わない機能なので、IDE 等で使用したい場合は不便です。

2016/11/11

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

Ubuntu で KeePass1 を便利に使っていたのですが、 いつの日か、ユーザ名やパスワードをコピーできなくなってしまいました2

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

参考

KeePass / Bugs / #1057 Copy to clipboard does not work at all (Linux mono)

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

$ sudo apt-get install  xsel:i386
  1. Ver 2.32 
  2. Ctrl + c が動作しない 
?
2016/11/09

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

2018/1/19 追記
ScanResult の level を RSSI に変換する方法を追記しました。

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

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

コード

私の所持している端末では、Wi-Fi が ON になっている時、定期的に周囲の Access Point の検索が走っているようです1

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

1
2
3
4
5
6
7
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
List<ScanResult> scanResults = wm.getScanResults();
 
for (ScanResult scanResult : scanResults) {
    Log.d(TAG, scanResult.toString());
}

ただ、これだと最後に取得した結果がいつのものなのかわかりません2。 そこで、検索が完了した時に呼ばれる BroadcastReceiver を登録し、 その onReceive() の中で先のコードを実行することで、最新のものを取得することにします。

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

  • WifiManager.startScan() は呼ばなくても検索が実行されていることが多い3

  • stopScan() という method はない

  • onReceive() は何度も呼ばれる

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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<ScanResult> 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 は 電波強度で、単位は デシベルミリワット[dBm] です。 これを、表示形式(いわゆる RSSI)に変換するには WifiManager.calculateSignalLevel(int, int) を使用します。

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

centerFreq0 : 5610
centerFreq1 : 0 
channelWidth : 2

パーミッション

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

1
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

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

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">

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() を普通に実行しても結果が空のリストになってしまう

  1. 多分、様々なアプリが WifiManager.startScan() を実行しているから? 
  2. ScanResulttimestamp という情報が乗っているので、実はわかるのですが 
  3. 先にも書いたように、様々なアプリが WifiManager.startScan() を実行しているからだと思われます 
?
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 に記述するだけでは不十分で、動的に許可を得ないといけない1ので注意が必要です。

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

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

1
2
3
4
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
<!-- getScanResults() にはこちらも必要 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

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

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

参考

Requesting Permissions at Run Time | Android Developers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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<ScanResult> scanResults = wm.getScanResults();
 
        for (ScanResult scanResult : scanResults) {
            Log.d(TAG, scanResult.toString());
        }
    }
}

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

  1. これも Android 6.0 からの仕様 
?
2016/11/07

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

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

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

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

参考

穀風: Android で Wi-Fi の接続状態を確認する

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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 が返ってくるのではなくSupplicantStateINACTIVE なインスタンスが返ってきます。

パーミッション

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

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">