2016/12/28

Android Studio でモジュールを削除する方法

Android Studioチェックしたバージョンは 2.2.3 でモジュールを削除しようとする時、
モジュールを右クリックしてコンテキストメニューを探してしまったことが一度くらいあるはず。
ところが、コンテキストメニューには "Delete" という項目はないのです。

では、どう削除するのかというと、"Project Structure..." メニューから削除できます。

File → Project Structure...
Modules で 対象のモジュールを選択し、"Delete" ボタン(マイナスのアイコン) を押す。
知っていれば何てこと無いのだけど、ちょっと直感的ではないです。 > 参考 > - [ide - How to delete a module in Android Studio - Stack Overflow](http://stackoverflow.com/questions/16710290/how-to-delete-a-module-in-android-studio) 参考リンクを見るとわかるように、仕様がどんどん変わっているようなので、そのうちコンテキストメニューに入るかもしれません。

2016/12/27

apk の署名を確認する方法2種

基本的には `keytool` を使っておけば良いと思うのですが、 `apksigner` でも確認出来たのでメモ。

署名済みの apk から署名を削除する方法

Debug Keystore で署名済みの apk が送られてきちゃった。みたいな事は時々あると思うのですが、そういう時は署名を削除して再署名すれば良いのです。

### apk の署名とは
apk の署名は以下の 3つのファイルで構成されています各ファイルの役割については参考リンクが詳しいので、そちらを参照してください。
そのため、これらのファイルを削除すれば、未署名の apk になります。

- META-INF/MANIFEST.MF
- META-INF/CERT.RSA
- META-INF/CERT.SF

注: CERT.RSA と CERT.SF は別の名前(ANDROID.RSA 等)になっている場合があります。

> 参考
> - [APK ファイルの署名の仕様 - urandroid](http://d.hatena.ne.jp/urandroid/20110818/1313656536)


### 署名を削除する
コマンドラインだと以下のようにすると良いでしょう。

```console
$ zip -d app-debug.apk "META-INF/*"
deleting: META-INF/CERT.RSA
deleting: META-INF/CERT.SF
deleting: META-INF/MANIFEST.MF
```

この方法だと `META-INF` ディレクトリ自体を削除してしまうのですが、apk の場合 `META-INF` にこれ以外のファイルが入ることは無さそうなのであくまで経験則。要確認。、これで大丈夫だと思われます。

以下のように `apksigner verify` をするとエラーが出ます。

```console
$ apksigner verify app-debug.apk
DOES NOT VERIFY
ERROR: Missing META-INF/MANIFEST.MF
```

> 参考
> - [apksigner | Android Studio](https://developer.android.com/studio/command-line/apksigner.html)

### 再署名する
未署名の apk に後から署名する方法がそのまま使えます。
`zipalign` を忘れないようにしましょう。

[未署名の apk に後から署名する方法 | 穀風](https://kokufu.blogspot.jp/2016/12/apk.html)

apksigner で未署名の apk に後から署名する方法

未署名の apk に署名するツールは、build-tools 24.0.3 以降から `jarsigner` の代わりに `apksigner` を使うようになりました。
`zipalign` の実行順序など細かいところが変わっていますのでご注意ください。

なお、署名済みの apk を未署名にする方法は以下を参照してください。

> 参考
> - [署名済みの apk から署名を削除する方法 | 穀風](http://kokufu.blogspot.jp/2016/12/apk_27.html)


### Overview
全体の流れとしては以下のようになります。

- zip のアライメント調整
- 署名
- 検証

具体的には以下。

```console
`gutter: false;
$ zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
$ apksigner sign --ks ~/.android/my-key.jks --ks-key-alias alias-name --out my-app.apk my-app-unsigned-aligned.apk
$ apksigner verify my-app.apk
```

`apksigner` と `zipalign` は `android-sdk/build-tools/XX.X.X/` にあり、ここではパスが通ってることとします実際には、build-tools のバージョン指定があってパスを通すのは結構面倒。

> 参考
>
> - [アプリに署名する | Android Studio](https://developer.android.com/studio/publish/app-signing.html#signing-manually)
> - [android - how to sign an already compiled APK - Stack Overflow](http://stackoverflow.com/questions/10930331/how-to-sign-an-already-compiled-apk)

一度経験すれば上記で十分だと思いますが、一応各手順の説明を書いておきます。


### zipalign
署名する前に zip のアライメントを整える必要がありますbuild-tools 24.0.2 以前の `zipalign` は `jarsigner` の実行以降に行っていたが、`apksigner` は実行前に行うとのこと。

```console
`gutter: false;
$ zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
```

- `-v` 冗長な情報表示
- `-p` 全ての共有ファイルで同じアライメントを使う
- `4` アライメント [byte]、少なくとも現在は 4 固定

> 参考
> - [zipalign | Android Studio](https://developer.android.com/studio/command-line/zipalign.html)


### 署名
署名ファイル `my-key.jks` は既に作成済みとします。作成方法は参考リンクを参照してください上のほうに Android Studio での作成方法も書いてあります。

```console
`gutter: false;
$ apksigner sign --ks ~/.android/my-key.jks --ks-key-alias alias-name --out my-app.apk my-app-unsigned-aligned.apk
```

`--out` を指定しなかった場合、元のファイルに上書きされます。
他の項目は自明ということで。

> 参考
> - [アプリに署名する | Android Studio](https://developer.android.com/studio/publish/app-signing.html#signing-manually)
> - [apksigner | Android Studio](https://developer.android.com/studio/command-line/apksigner.html)


### 検証
最後に `apksigner` を利用して検証します。

```console
`gutter: false;
$ apksigner verify my-app.apk
```

成功した場合、何も表示されないので注意が必要です。

以下のように `-v` か `--print-certs` をつけた方がわかりやすいかもしれません。

```console
`gutter: false;
$ apksigner verify -v app-debug.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Number of signers: 1
```

```console
`gutter: false;
$ apksigner verify --print-certs app-debug.apk 
Signer #1 certificate DN: CN=Android Debug, O=Android, C=US
Signer #1 certificate SHA-256 digest: ca17bec5dd187fdb4a859b22f5db82b5251ab68b041df5b05dd885432771d5cd
Signer #1 certificate SHA-1 digest: 13199c0c7fdf8734d904925f5aa425c537cf8e69
Signer #1 certificate MD5 digest: ef94f75750de7d124796eb7c5ed38cf2
```

2016/12/20

Android の build.prop に設定値を追加する方法

Android のシステムプロパティを設定する方法は多々あります。

> 参考
>
> [システムプロパティのあれこれ | まくまく Android ノート](http://maku77.github.io/android/vendor/system-property.html)

その中でもデバイス固有のプロパティを記述するのが `/system/build.prop`。

OS を独自ビルドするような仕事をしていると、この `/system/build.prop` に設定値を追加したいことがあるのですが、このファイルの元になる情報が分散しているので、まとめておきます。

2016/12/15

MM-BTUD44 を Ubuntu 16.04 で使ってみた

Bluetooth ドングル [MM-BTUD44](http://amzn.to/2hvA2Fn) を買ってみました。
Ubuntu は対応OS に含まれていませんが、私のマシンでは問題なく使えました。 マザーボードは Intel H97 系の [GIGABYTE GA-H97-D3H](http://amzn.to/2hPRQrl) あくまで、私の環境では動作したという報告になります。 メーカーのサポート対象ではないので、ご購入の際はあくまで **自己責任** でお願いしますお約束

2016/12/03

Android で Wi-Fi Access Point との接続状態変化をイベントとして取得する


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

Wi-Fi の接続状態をイベントとして取得するには `"android.net.wifi.STATE_CHANGE"` action を監視する `BroadcastReceiver` を使います。

`"android.net.wifi.WIFI_STATE_CHANGED"` という action もありますが、こちらは、Wi-Fi 機能の状態変化を見るものです。
非常に紛らわしいので注意が必要です。

> 参考
>
> [穀風: Android で Wi-Fi 機能の有効・無効の変化をイベントとして取得する](http://kokufu.blogspot.jp/2016/10/android-wi-fi_20.html)

### BroadcastReceiver のコード
`NetworkInfo` が Extra として渡されるので、接続状態を確認することができます。

```java
public class WifiConnectionWatcher extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { // "android.net.wifi.STATE_CHANGE"
            NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
            switch (info.getState()) {
                case DISCONNECTED:
                    break;
                case SUSPENDED:
                    break;
                case CONNECTING:
                    break;
                case CONNECTED:
                    break;
                case DISCONNECTING:
                    break;
                case UNKNOWN:
                    Log.e(TAG, "Wifi connection state is UNKNOWN");
                    break;
                default:
                    Log.e(TAG, "Wifi connection state is OTHER");
                    break;
            }
        }
    }
}
```

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


### 登録方法1
AndroidManifest.xml に登録する方法

常に変化を監視したい場合は、以下のように `BroadcastReceiver` を AndroidManifest.xml に追記します。

```xml

    
        
    

```

### 登録方法2
コードで登録する方法

特定の Activity が起動している間だけ監視したい場合などは、以下のように動的に登録します。

```java
    // Activity 等の Context の中で
    WifiConnectionWatcher mWifiConnectionWatcher = new WifiConnectionWatcher();

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

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); // "android.net.wifi.STATE_CHANGE"

        registerReceiver(mWifiConnectionWatcher, intentFilter);
    }

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

        unregisterReceiver(mWifiConnectionWatcher);
    }
```

### パーミッション
このコードを実行するのに特別なパーミッションは必要ありません。

2016/12/02

Android で Wi-Fi Access Point との接続を切断する


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

### 接続切断の方法は2種類ある
Wi-Fi Access Point との接続を切断するには `WifiManager.disconnect()` もしくは  `WifiManager.disableNetwork()` を使用します。

`WifiManager.disconnect()` を使用した場合、状態は "Saved"日本語だと「保存済み」 になります。
`WifiManager.disableNetwork()` を使用した場合、状態は "Disabled"日本語だと「無効」になります。
"Disabled" といっても、登録してあるパスワード等が消えてしまったわけではありません。 設定画面から "Connect" をクリックしたり、 `WifiManager.enableNetwork()` を使用することでパスワード入力無しに再接続することが可能です。 ### 違いはあるようで無い では何が違うのかというと、**自動で** 再接続するかどうかのようです。 "Saved" の場合、何らかのタイミングで OS が再接続を試みる可能性がありますが、"Disabled" の場合はユーザーもしくは、`android.permission.CHANGE_WIFI_STATE` パーミッションを持ったアプリが意思を持って接続しない限り再接続されません。 ただし、後述のようにデバイスを再起動した場合は "Disabled" でも再接続されるので、完全に無効化するわけではないようです。 > 参考 > > [Android - What´s the difference between WifiManager disableNetwork() and disconnect() - Stack Overflow](http://stackoverflow.com/questions/30094055/android-what%C2%B4s-the-difference-between-wifimanager-disablenetwork-and-disconn) また、"Saved" もどのタイミングで再接続されるのかは不定のようです。 上記のサイトでは `WifiManager.disconnect()` を呼んでから数秒後に再接続されたとありますが、 私の Nexus 7 (2012)Android 4.4.4 では 一度 "Saved" 状態になると自動で再接続はされませんでした一度 Wi-Fi の圏外に出る等いろいろやってみたのですが。 ただし、デバイスを再起動すると接続されます。これは "Disabled" でも同じ。 結局、私の Nexus 7 では、これらの違いがよくわかりませんでした。 コードで Wi-Fi の接続を制御したい場合、勝手に再接続されることはあまり想定しないと思われるので、 `WifiManager.disableNetwork()` を使っておくのが無難でしょう。 ### コード1 `WifiManager.disconnect()` を使用する方法です。 ```java // Activity 等の Context 内で WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE); boolean succeeded = wm.disconnect(); ``` ### コード2 `WifiManager.disableNetwork()` を使用する方法です。 引数として Network ID が必要ですので、 `WifiManager.getConnectionInfo()` を使用して接続している Access Point の情報を取得します。 ```java // Activity 等の Context 内で WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE); WifiInfo wifiInfo = wm.getConnectionInfo(); boolean succeeded = wm.disableNetwork(wifiInfo.getNetworkId()); ``` 未接続状態でも `wifiInfo` は `null` にならず、Network ID が `-1` のインスタンスが返ってきます。 > 参考 > > [穀風: Android で 接続している Wi-Fi Access Point の情報を取得する](https://kokufu.blogspot.jp/2016/11/android-access-point.html) ### パーミッション `WifiManager.disconnect()` や `WifiManager.disableNetwork()` を実行するには `android.permission.CHANGE_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。 ```xml ``` また、 `WifiManager.getConnectionInfo()` を実行するには `android.permission.ACCESS_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。 ```xml ```

2016/12/01

Android Support Library のバージョン一覧を取得する

使用できる Support Library のバージョン一覧は maven レポジトリの中を参照することで可能です。

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

```

2016/10/30

Android エミュレーターの rootfs に永続的な編集を加える方法

[Android エミュレーターで Read-only file system に書き込めるようにする](http://kokufu.blogspot.jp/2016/10/android-read-only-file-system.html) で rootfs は揮発性だと書きました。
つまり、再起動すると加えた変更が元に戻ってしまうのです。

これは、rootfs が [Initramfs](https://ja.wikipedia.org/w/index.php?title=Initramfs&redirect=no) という RAM 上に展開されるファイルシステムのためです。
RAM 上に展開されたファイルシステムに変更を加えても、その実体には変更が反映されないので
再起動後には消えてしまうというわけです。

この Initramfs、実体は [cpio](https://ja.wikipedia.org/wiki/Cpio) アーカイブを [gzip](https://ja.wikipedia.org/wiki/Gzip) 圧縮したものなので、以下のようにして永続的に変更を反映させることができます。

> 参考
>
> [第384回 Initramfsのしくみ:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社](http://gihyo.jp/admin/serial/01/ubuntu-recipe/0384)


### ramdisk.img の場所を確認
`~/.android/avd/AVD_NAME/hardware-qemu.ini` の `disk.ramdisk.path` が rootfs の実体となるファイルを示しているので確認します。

```
disk.ramdisk.path = ANDROID-SDK_DIR/system-images/android-21/default/x86_64//ramdisk.img
```


### ramdisk.img の展開
空のディレクトリ名前は何でも良いですを作り、先ほど確認した `ramdisk.img` を展開します。

```console
`gutter: false;
$ mkdir ramdisk
$ cd ramdisk
$ gunzip -c ANDROID-SDK_DIR/system-images/android-21/default/x86_64/ramdisk.img | cpio -i
```

`ramdisk` dir 以下に ramdisk.img の中身が展開されているはずです。


### 編集
ファイルの追加や編集を行います。

起動に使用する設定ファイルやスクリプトを書き換えると、正常に起動しなくなる可能性があるので注意しましょう。


### 再アーカイブ
`ramdisk` ディレクトリにいることを確認し、以下のように圧縮します。

```console
`gutter: false;
$ pwd
~/ramdisk
$ find . | cpio -R 0:0 -o -H newc | gzip > ../my_ramdisk.img
```

### エミュレーターに登録
以下のようにして、`ramdisk.img` を入れ替えれば、エミュレーターからマウントされるようになります。

```console
`gutter: false;
$ cd ANDROID-SDK_DIR/system-images/android-21/default/x86_64
$ mv ramdisk.img ramdisk.img.bak
$ cp ~/my_ramdisk.img ramdisk.img
```

本来ならば、元の `ramdisk.img` を書き換えるのは避けたいところです。
しかし、`~/.android/avd/AVD_NAME/hardware-qemu.ini` を編集してもエミュレーターの起動時にデフォルト設定に書きなおされてしまいますGUI の AVD Manager が原因かと疑ったのですが、コマンドラインから起動しても同じでした。
そのため、今回は `ramdisk.img` を直接変更する方法をとりました。

2016/10/29

Android エミュレーターで Read-only file system に書き込めるようにする

エミュレーター、もしくは root をとったデバイスでも、Read-only file system としてマウントされているディレクトリは、書き込み・編集をすることが出来ません。

しかし、以下のように書き込み権限をつけてリマウントすれば、編集可能になります。

### マウント状態の確認
`adb shell` でエミュレーターにログインし、以下のようにマウント状態を確認。
`ro` がついているのが Read-only file system です。

```console
`highlight: [3, 14]; gutter: false;
$ adb shell
# mount
rootfs / rootfs ro,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,relatime,mode=755 0 0
devpts /dev/pts devpts rw,relatime,mode=600 0 0
proc /proc proc rw,relatime 0 0
sysfs /sys sysfs rw,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,relatime 0 0
none /acct cgroup rw,relatime,cpuacct 0 0
none /sys/fs/cgroup tmpfs rw,relatime,mode=750,gid=1000 0 0
tmpfs /mnt/asec tmpfs rw,relatime,mode=755,gid=1000 0 0
tmpfs /mnt/obb tmpfs rw,relatime,mode=755,gid=1000 0 0
none /dev/cpuctl cgroup rw,relatime,cpu 0 0
/dev/block/vda /system ext4 ro,relatime,data=ordered 0 0
/dev/block/vdb /cache ext4 rw,nosuid,nodev,noatime,errors=panic,data=ordered 0 0
/dev/block/vdc /data ext4 rw,nosuid,nodev,noatime,errors=panic,data=ordered 0 0
/dev/block/vold/253:48 /mnt/media_rw/sdcard vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/block/vold/253:48 /mnt/secure/asec vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/fuse /storage/sdcard fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
```

2016/10/28

bootanimation.zip は圧縮してはいけない

Android 起動時のアニメーションは `/data/local/bootanimation.zip`Android 5.0 Lollipop 以降は `/oem/media/bootanimation.zip` に変更になった様子 か `/system/media/bootanimation.zip` で決まります。

この `bootanimation.zip` ファイル、PNG ファイルを連番で用意し、構成ファイル `desc.txt` をトップに格納するというシンプルな構成なので簡単に作成できます。

> 参考
>
> [Deciphering Android's bootanimation.zip desc.txt](https://blog.justinbull.ca/making-a-custom-android-boot-animation/)

しかし、普通に zip ファイルを作るとうまく表示されません場合によっては起動すらしなくなっちゃうことも。
この zip ファイル、無圧縮でなければいけないのです。

それに気づかず、数時間使ってしまったー実は参考リンク先にも圧縮してはいけないと書いてありました。


### 無圧縮zip の作り方
Linux 系だと以下のように `zip` コマンドで作成します。

```console
`gutter: false;
$ zip -0 bootanimation.zip desc.txt part0/* part1/*
```

### 実際にアニメーションを表示しているソースコード
バージョンによって微妙に挙動が変わっているので注意が必要です。

https://android.googlesource.com/platform/frameworks/base/+/master/cmds/bootanimation/

ブートアニメーションをいじろうという人は [AOSP](https://source.android.com/source/index.html) 等から自分でビルドする人かと思うのでエミュレーターでも出来ますが、それやっても、あまり面白くないからなぁ、
コードは手元にあるという前提なのでしょう。


### いろいろなブートアニメーション
蛇足ですが、様々なデバイスのブートアニメーションを集めたサイトがあったのでリンクしておきます。

[Boot Animations « Droidboots](http://droidboots.com/downloads/boot-animations/)

2016/10/27

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


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

Wi-Fi 接続して、通信を行っているか等の状態を確認する方法です。

Android の Wi-Fi 接続状態を保持するクラスは `WifiInfo` と `NetwrokInfo` の2種類があります。
前者は Wi-Fi に特化しているのに対し、後者は接続全般LTE等をカバーします。
状況によって使い分けるのが良いでしょう。


### WifiInfo を使うコード
`wpa_supplicant` が提供する情報を取得する方法です。

Wi-Fi 接続していない場合も `null` が返る**のではなく**、`INACTIVE` な状態が返ります。 

```java
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wm.getConnectionInfo();

WifiInfo.SupplicantState state = wifiInfo.getSupplicantState();
```

取得できる SupplicantState
```java
public enum SupplicantState implements Parcelable {
    DISCONNECTED,
    INTERFACE_DISABLED,
    INACTIVE,
    SCANNING,
    AUTHENTICATING,
    ASSOCIATING,
    ASSOCIATED,
    FOUR_WAY_HANDSHAKE,
    GROUP_HANDSHAKE,
    COMPLETED,
    DORMANT,
    UNINITIALIZED,
    INVALID
}
```

各状態については JavaDoc を参照のこと。

> 参考
>
> [SupplicantState | Android Developers](https://developer.android.com/reference/android/net/wifi/SupplicantState.html) 

この `SupplicantState` ですが、JavaDoc の中に以下のような記述があります。

> This is more fine-grained than most users will be interested in.
> In general, it is better to use `NetworkInfo.State`.

意訳すると以下のような感じでしょうか

> この情報は多くのユーザーにとって詳細すぎる。一般的には `NetworkInfo.State` を使うほうが良い。

特別な理由が無いのであれば `NetworkInfo` を使っておいた方が良いのかもしれません。


### NetworkInfo を使うコード1
`getActiveNetworkInfo()` で現在使用中のネットワーク情報を取得し、それが Wi-Fi かどうか確認するという方式です。

なお、ネットワーク接続がない状態だと `networkInfo` が `null` になります。

```java
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        // シンプルな状態を取得
        NetworkInfo.State networkState = networkInfo.getState();

        // 詳細状態を取得
        // これを使うことはめったに無いと思われる
        NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
}
```

取得できる State
```java
public enum {
    CONNECTING,
    CONNECTED,
    SUSPENDED,
    DISCONNECTING,
    DISCONNECTED,
    UNKNOWN
}
```

取得できる DetailedState
```java
public enum {
    /** Ready to start data connection setup. */
    IDLE,
    /** Searching for an available access point. */
    SCANNING,
    /** Currently setting up data connection. */
    CONNECTING,
    /** Network link established, performing authentication. */
    AUTHENTICATING,
    /** Awaiting response from DHCP server in order to assign IP address information. */
    OBTAINING_IPADDR,
    /** IP traffic should be available. */
    CONNECTED,
    /** IP traffic is suspended */
    SUSPENDED,
    /** Currently tearing down data connection. */
    DISCONNECTING,
    /** IP traffic not available. */
    DISCONNECTED,
    /** Attempt to connect failed. */
    FAILED,
    /** Access to this network is blocked. */
    BLOCKED,
    /** Link has poor connectivity. */
    VERIFYING_POOR_LINK,
    /** Checking if network is a captive portal */
    CAPTIVE_PORTAL_CHECK
}
```


### NetworkInfo を使うコード2 (API Level 21 Lollipop 以降) 
`getAllNetworks()` で使用可能な ~~全ての~~ ネットワークを取得して、その中から Wi-Fi のものをチェックする方法です。

この方法の落とし穴は、`getAllNetworks()` が **接続している** ネットワーク一覧しか返さないというところです。
つまり、一般的な端末では `getAllNetworks().length` は大抵 0 か 1 になります名前と挙動が合ってないし、あんまり使い勝手が良くないと思うのは私だけ?。
ただし、モバイル接続から Wi-Fi 接続に切り替わる瞬間、少しの間だけ 2 になることもあるようです少なくとも、私の手元の Nexus 7 (2012) と Nexus 6P はそういう挙動でした。

そのため、この方法は実質「NetworkInfo を使うコード1」と一緒と言って良いでしょう。

```java
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

for (Network network : connectivityManager.getAllNetworks()) {
    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        // シンプルな状態を取得
        NetworkInfo.State state = networkInfo.getState();

        // 詳細状態を取得
        NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
    }
}
```

取得できる状態は「NetworkInfo を使うコード1」と同じです。


### NetworkInfo を使うコード3 (API Level 20 Kitkat 以前) 
この方法は Lollipop 以降 Deprecated になりました。

なお、Wi-Fi 機能がハードウェア的に存在しない場合は、`networkInfo` が `null` になります。

```java
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

NetworkInfo networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

// シンプルな状態を取得
NetworkInfo.State state = networkInfo.getState();

// 詳細状態を取得
NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
```

取得できる状態は「NetworkInfo を使うコード1」と同じです。


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

```xml

```

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

```xml

```

2016/10/26

Android で登録済みの Wi-Fi Access Point 一覧を取得する


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

既に認証済みで接続可能な Wi-Fi Access Point の一覧を取得する方法です。
### コード ```java // Activity 等の Context の中で WifiManager wm = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE); List wifiConfigurations = wm.getConfiguredNetworks(); if (wifiConfigurations != null) { // Wi-Fi が OFF の場合等は null の可能性がある for (WifiConfiguration configuration : wifiConfigurations) { // configuration.SSID; // configuration.networkId; // configuration.status; // ...etc } } ``` ### パーミッション このコードを実行するには `android.permission.ACCESS_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。 ```xml ```

2016/10/21

Android Studio で Update&Restart がうまくいかない時の対処方法

Unity LauncherUbuntu 16.04 LTS に Android Studio を登録してから、"Update&Restart" を押しても Update が行われなくなってしまいました。

Android Studio が終了するものの、その後何も起こらず、手動で起動してもアップデートされていないのです。

で、調べてみたところ、既に報告がありました。

> 参考
>
> [Android Studio: "Update & Restart" doesn't work - Stack Overflow](http://stackoverflow.com/questions/25379665/android-studio-update-restart-doesnt-work)

Android Studio を起動する時に余計なオプションをつけると、うまくいかないようです。

また、`android-studio` のインストールされているディレクトリの書き込み権限がない場合も同じような問題が起こります。
こちらはエラーメッセージにその旨が表示されるのでわかりやすいです。

2016/10/20

Android で Wi-Fi 機能の有効・無効の変化をイベントとして取得する


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

Wi-Fi の状態変化を知るためには `"android.net.wifi.WIFI_STATE_CHANGED"` action を監視する `BroadcastReceiver` を使います。

### BroadcastReceiver のコード
`WifiManager.getWifiState()` で取得できる状態に変化が起こった場合に呼ばれるコードです。

```java
public class WifiStateWatcher extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { // "android.net.wifi.WIFI_STATE_CHANGED"
            // 変化前の状態を取得
            int previousState = intent.getIntExtra(
                    WifiManager.EXTRA_PREVIOUS_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);

            // 変化後の状態を取得
            int state = intent.getIntExtra(
          WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);

            // 取得できるのは以下の状態
            // WifiManager.WIFI_STATE_DISABLED
            // WifiManager.WIFI_STATE_DISABLING
            // WifiManager.WIFI_STATE_ENABLED
            // WifiManager.WIFI_STATE_ENABLING
            // WifiManager.WIFI_STATE_UNKNOWN
        }
    }
}
```

> 参考
>
> [穀風: Android で Wi-Fi 機能が現在有効かどうか調べる](http://kokufu.blogspot.jp/2016/10/android-wi-fi_60.html)


### 登録方法1
AndroidManifest.xml に登録する方法

常に変化を監視したい場合は、以下のように `BroadcastReceiver` を AndroidManifest.xml に追記します。

```xml

    
        
    

```

### 登録方法2
コードで登録する方法

特定の Activity が起動している間だけ監視したい場合などは、以下のように動的に登録します。

```java
    // Activity 等の Context の中で
    WifiStateWatcher mWifiStateWatcher = new WifiStateWatcher();

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

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); // "android.net.wifi.WIFI_STATE_CHANGED"

        registerReceiver(mWifiStateWatcher, intentFilter);
    }

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

        unregisterReceiver(mWifiStateWatcher);
    }
```

### パーミッション
このコードを実行するのに特別なパーミッションは必要ありません。

2016/10/19

Android で Wi-Fi 機能を有効・無効にする


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

Wi-Fi 機能の有効・無効を切り替えるには、`WifiManager.setWifiEnabled()` を実行します。

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

// 有効にする
wm.setWifiEnabled(true);

// 無効にする
wm.setWifiEnabled(false);
```

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

```xml

```

### 現実的な実装
実際は、以下のように Wi-Fi の状態を確認してから使用することが多くなると思います以下のコードを実行する場合は、`android.permission.ACCESS_WIFI_STATE` パーミッションも設定する必要があります。

```java
// Click する度に On/OFF を切り替える
@Override
public void onClick(View v) {
    WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
    
    if (wm.isWifiEnabled()) {
        // Wi-Fi が有効な場合は無効にする
        wm.setWifiEnabled(false);
    } else {
        // 有効にしている最中は意味がないので、状態を確認
        // ただし、WIFI_STATE_ENABLING 中に setWifiEnabled() を呼んでも問題はない
        if (wm.getWifiState() != WifiManager.WIFI_STATE_ENABLING) {
            wm.setWifiEnabled(true)
        }
    }
}
```

参考
[穀風: Android で Wi-Fi 機能が現在有効かどうか調べる](http://kokufu.blogspot.jp/2016/10/android-wi-fi_60.html)

Android で Wi-Fi 機能が現在有効かどうか調べる


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

Wi-Fi 機能の ON/OFF を調べます。
また、Wi-Fi の ON/OFF 切り替え時には状態があり、より正確な制御をするためには、状態を確認する必要があります。

### コード

```java
// Activity 等の Context 内で

// Wi-Fi ON/OFF の取得
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
boolean isWifiEnabled = wm.isWifiEnabled();

// 状態の取得
// WifiManager.WIFI_STATE_DISABLED
// WifiManager.WIFI_STATE_DISABLING
// WifiManager.WIFI_STATE_ENABLED
// WifiManager.WIFI_STATE_ENABLING
// WifiManager.WIFI_STATE_UNKNOWN
int wifiState = wm.getWifiState()
```

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

```xml

```

### isWifiEnabled と wifiState との関係
`android.net.wifi.WifiManager` のソースコードAndroid 7.0 Nougatは以下のようになっています。
つまり、Wi-Fi の変化時、`isWifiEnabled` は `false` であり、細かい状態を `wifiState` から取得しなければなりません。

```java
public boolean isWifiEnabled() {
    return getWifiState() == WIFI_STATE_ENABLED;
}
```


### GUI上での表示
GUI 上では Wi-Fi の設定から確認可能な項目です。

設定 → 無線とネットワーク → Wi-Fi

Android の Wi-Fi 実装に関する情報のまとめ

ここ数日、Android の Wi-Fi まわりの実装をしていて、その際に必要になった情報をまとめておこうと思います。

Android で Wi-Fi 機能が備わっているか調べる


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

ハードウェアとして Wi-Fi 機能が備わっているか調べる方法です。

### コード

```java
// Activity 等の Context 内で
PackageManager pm = getPackageManager();
boolean hasWifi = pm.hasSystemFeature(PackageManager.FEATURE_WIFI);
```

### パーミッション
特別なパーミッションは必要ありません

2016/10/06

Proguard が "Ignoring InnerClasses attribute for an anonymous inner class" という Warning を出した場合の修正方法

Android Studio を更新したら、Proguard が "Ignoring InnerClasses attribute for an anonymous inner class" という Warning を大量に吐くようになってしまいました。

2016/09/13

インストールしているのに Sources for 'Android API 24 Platform' not found. が出てしまう

@Android Studio 2.1.3

`compiledSdkVersion` を 24 にしたところ、`Sources for 'Android API 24 Platform' not found.` が出るようになってしまいました。
以下のようにソースコードはインストールしているのにも関わらず。

File → Settings → Appearance & Behavior → System Settings → Android SDK
`Refresh` を押してもウンともスンとも言いません。

2016/09/08

React.render is not a function

今更ながら [React](https://facebook.github.io/react/) を使い始めました。
しかし、サンプルを動作させようとしたところ、`render` function が無いとエラーが発生。

```
Uncaught TypeError: React.render is not a function
```

2016/08/24

Android Studio で Documentation がポップアップされるようになったので修正

> Ubuntu 16.04 LTS  
> Android Studio 2.1.3

Android Studio をクリーンインストールしたら、`Ctrl + Space` するたびCode Completion を表示するたびに `Documentation` なるウィンドウがポップアップするようになってしまいました。
これ、とても鬱陶しいので修正しました。

2016/08/04

最新版の update-java-alternatives は javac を更新してくれない

@[Ubuntu](http://www.ubuntu.com/) 16.04 LTS

先日、[update-java-alternatives に Oracle java も追加する](https://kokufu.blogspot.jp/2016/07/update-java-alternatives-oracle-java.html) という投稿をしたのですが、
使っていると想定した動作をしていないことがわかりました。

2016/07/31

update-java-alternatives に Oracle java も追加する

@[Ubuntu](http://www.ubuntu.com/) 16.04 LTS

2016/8/4 追記
最新の update-java-alternatives は javac 等を更新してくれないバグがあります。 一時的なものだと思われますが、ご注意ください。

[穀風: 最新版の update-java-alternatives は javac を更新してくれない](https://kokufu.blogspot.jp/2016/08/update-java-alternatives-javac.html)
`update-java-alternatives` には `--install` のようなコマンドがないため、簡単に対象を追加することは出来ません一般的にはパッケージから入れた時に自動的に追加される。 ただ、その中身は単なる `update-alternatives`-java- がついていない のラッパースクリプトなので、動作を理解すれば、任意の JDK (JRE) を手動で登録して、切り替えられるようにすることが可能です。 今回は [Oracle](http://www.oracle.com/technetwork/java/javase/downloads/index.html) から jdk-8u101 をダウンロードして追加してみました。

2016/07/13

KeePass2 on Ubuntu の文字化けを直す

[Ubuntu](http://www.ubuntu.com) 16.04 LTS にしたら [KeePass2](http://keepass.info) のサーチ窓が文字化けするようになってしまいましたKeePass2 のバージョンは 2.32 (Dev)
Ubuntu のバージョンより KeePass2 のバージョンの問題でしたね。
各項目の文字化けは[以前対応した](http://kokufu.blogspot.jp/2013/02/keepass2-on-ubuntu-1204-lts.html)のですが、検索窓は初めて。

2016/07/12

Ubuntu のシステムフォントを変更する方法

@[Ubuntu](http://www.ubuntu.com) 16.04 LTS

いつの間にか `System Settings...` からシステムフォントの設定が消えていました勝手にいじるなってことなんでしょうか?しかし、マルチバイト言語ユーザには不便だ。
普段、日本語化しないで使っているため必要なかったのですが、いざ変更しようとするとこれは不便。
というわけで、方法をメモっておきますDesktop 環境が Unity の場合

2016/07/05

Atom で notification を表示する方法

[Atom](https://atom.io) package でユーザへの通知を実現するには notification を使用します実は `alert` も使えたりします。

> 参考
> [NotificationManager - Atom](https://atom.io/docs/api/v1.8.0/NotificationManager)

2016/06/23

Atom で Package をリロードする方法

[Atom](https://atom.io/) の Package 開発時には、都度 Package をリロードして変更を反映させる必要があります。  
調べてみたところ、特定の Package をリロードするのではなく、Atom 自身を再起動させるという方針のようです。

> 参考  
> [Package: Modifying Text](http://flight-manual.atom.io/hacking-atom/sections/package-modifying-text/)
> の "Reload the Package"

2016/05/28

"Gradle version x.xx is required" が出たときの対処方法

[Android Studio](https://developer.android.com/studio/index.html) を 2.1.1 にしたら以下のように [Android Gradle Plugin](https://developer.android.com/studio/releases/gradle-plugin.html) のアップデートを促すダイアログが。

2016/05/16

Chrome 間でブックマーク等の同期が出来なくなった場合の対処方法

自分用メモ

Chrome の[同期機能](https://support.google.com/chrome/answer/165139?hl=ja)がうまく動かなかったら、
[Chrome Sync](https://www.google.com/settings/chrome/sync) にアクセスして「同期をリセット」してみると良い。

その後、Chrome の設定からログインし直す。

2016/05/13

Emacs で日本語を保存しようとすると "undecided-unix cannot encode" というエラーが出るようになったので修正してみた

### 日本語のメッセージ保存でエラー
私は [Git](https://git-scm.com/) のエディタとして [Emacs](https://www.gnu.org/software/emacs/) を使用しています。  
つい最近まで問題なく使用していたのですが、ここ最近、日本語(マルチバイト)のメッセージを保存しようとすると以下のようなエラーが出るようになってしまいました。  
都度、コーディングシステムを指定すれば保存は出来るのですが、不便なので原因を探ってみました。

2016/04/24

Python の FTP_TLS で "522 Data connections must be encrypted." というエラーが出たら

login の後に `FTP_TLS.prot_p()` を呼ぶ
```python
from ftplib import FTP_TLS

ftp = FTP_TLS(site, user, password)
try:
    ftp.prot_p()
    # 何か処理
finally:
    ftp.quit()
```

2016/04/21

Python 3 で配列の型を一括変換する方法

自分のための備忘録として (for Python 3動作確認は Python 3.4.3)

以下のような文字列配列を `int` に変更する
```python
>>> data
['12', '345', '6789']
```

### map を使う方法
```python
>>> list(map(int, data))
[12, 345, 6789]
```
Python 3.1 より以前は `list()` は必要ないので注意。
> 参考  
> [Python3でmapがmap objectを返す(ようになった)件 | swimmingpython blog](http://swimmingpython.net/ja/?p=565)

### リスト内包表記List Comprehensionsを使う方法
```python
>>> [ int(x) for x in data]
[12, 345, 6789]
```

2016/04/18

Nautilus を開くと常に隠しファイルが表示されるようになってしまった

@Ubuntu 15.10

いつの頃からか Nautilus を開くと、いつも隠しファイルが表示されるようになってしまいました。
`Ctrl-h` で表示されなくなるものの、Window を閉じてもう一度開くとまた表示されてしまいます。
### gnome のバグだった 別に大きな問題になるわけではないのですが、気持ち悪いので調べてみたところ gnome のバグみたいです。 2015年になって再発した模様。 > 参考 > [Bug #1171852 “Hidden files are displayed by default” : Bugs : Ubuntu GNOME](https://bugs.launchpad.net/ubuntu-gnome/+bug/1171852) ### 回避方法 私の環境では以下の方法で問題は起こらなくなりました。 ``` $ dconf-editor ``` gtk → settings → file-chooser → show-hidden を消す
ただ、この方法はあくまで一時的な回避方法です。 Software Update をかけたりすると、元に戻ることがありますそう頻繁ではないのですが、その都度直さなければなりません。 次のメジャーアップデートまでにはちゃんと直ると良いのですが他力本願だなぁ…

2016/03/18

m2ts ファイルを無劣化で mov に変換する方法

AVCHD で保存した動画を一気に変換する必要があったのでメモ。

2016/03/12

marked.js の code highlight を SyntaxHighlighter にする

このブログに [marked.js](https://github.com/chjj/marked) を適用して [Markdown でブログを書ける](https://kokufu.blogspot.jp/2016/03/blogger-markdown-blockquote.html)ようにしました。
marked.js は Code Highlighter に任意のライブラリを使用できるよう設計されている素晴らしい!ので、これまで通り[SyntaxHighlighter](http://alexgorbatchev.com/SyntaxHighlighter/) を使えるようにしました。

2016/03/11

Blogger を Markdown で書けるようにしたけど、Blockquote が表示されない

[marked.js](https://github.com/chjj/marked)
を使用すれば Blogger でも Markdown で記事が書けそうなので、ちょっとやってみました。

> 参考  
> [BloggerでMarkdown書けるようにした。 - Qiita](http://qiita.com/her0m31/items/1804bdc251a647e0e9a8)

ただ、そのままだと Blockquote が上手く処理できず、ちょっと工夫してやる必要があったので書いておきますこの記事も Markdown で書いているので、「参考」の部分がきちんとレンダリングされていればうまくいっている証拠

2016/02/14

AdView は動的追加にしないとメモリリークする

私が Google Play で公開している Dive! Log! というアプリに少し大きめの画像を扱う機能を追加したところ、どうもメモリリークしているような挙動を示すようになりました。 Heap Dump を見てみたところ、Google AdMob の AdView まわりが参照を握っている様子。
こういう時の常套手段として、AdView しかないアプリを作って動作を確認してみることにしました。

2016/02/11

Android 6.0 で EXTERNAL_STORAGE へアクセス許可しても即時反映されない

Android 6.0 以降から個別のパーミッション設定が可能になった

Android 6.0 Marshmallow 以降では各パーミッションを個別に設定できるようになりました。 ユーザにとっては有り難い機能ですが、開発者としてはエラーの原因が増えるので辛いところです。

Storage 関連のパーミッションはすぐに反映されない

特に android.permission.WRITE_EXTERNAL_STORAGEandroid.permission.READ_EXTERNAL_STORAGE は許可を動的に取得してもすぐに反映されないので注意が必要です。
というのも、External Storage へのアクセスは Linux の基本機能であるファイルへのアクセス制限を用いて実現されているからです。
具体的には sdcard_rw というグループへのアクセス許可があるかどうかを Linux Kernel が判断しています。そして、実行中のプロセスに関しては設定が反映されませんAndroid OS の動作が今後変わる可能性はありますが

反映させるためにはアプリを再起動する

少なくとも現時点では、この問題を回避する方法はアプリごと再起動するしか解が無いように思います。

具体例

実際のコードを載せておきます最低限のものなので、細かいお作法に関しては公式のドキュメントを参考にしてください
まず、Activity の onResume など使用する直前にパーミッションを確認します。

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

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                REQUEST_PERMISSIONS_WRITE_EXTERNAL_STORAGE);
    }
}

WRITE_EXTERNAL_STORAGE への許可がない場合、以下のようにユーザへ許可を求めるダイアログが表示されます。

ユーザの対応が onRequestPermissionsResult で返ってくるので、対応を決めます。一般的なパーミッションだとそのまま処理を続行することになりますが、Storage 系のパーミッションの場合、以下のように アプリの再起動をして設定を反映させますActivity ではなくアプリプロセスの再起動が必要です

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
        @NonNull int[] grantResults) {
    switch (requestCode) {
        case REQUEST_PERMISSIONS_WRITE_EXTERNAL_STORAGE:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 許可された場合
                // 設定を反映させるためにアプリを再起動
                Intent activityIntent = new Intent(this, ThisActivity.class); // 自Activity を指定
                int pendingIntentId = 1;
                PendingIntent pendingIntent = PendingIntent.getActivity(this, pendingIntentId, activityIntent, PendingIntent.FLAG_CANCEL_CURRENT);
                AlarmManager mgr = (AlarmManager)getSystemService(ALARM_SERVICE);
                mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent);
                System.exit(0);
            } else {
                // 許可されなかった場合
                // TODO エラーメッセージを表示して Activity を終了させる等
            }
            break;
        default:
            // Do nothing
            break;
    }
}

参考

2016/01/27

drawable の横にビックリマークが表示されるのはエラーではない

@ Android Studio 1.4

Android Studio で layout を編集していると drawable の横にビックリマーク (Exclamation mark) が表示されることがあります。
結論からいうと、これはエラーではありません。
Android Studio がサムネイルをレンダリング出来なかったために表示されるマークです。
大抵の場合、drawable に XML を指定している場合に表示されるはずです。

このエリアにサムネイル表示される機能は便利ですが、この表示の仕方はちょっと紛らわしいですね。

2016/01/13

VirtualBox の Shared Folders でシンボリックリンクを作れるようにする

デフォルトでは Shared Folders 上にシンボリックリンクを作成することはできない

VirtualBox のクライアントOS が Unix系の場合、その Shared Folders 上でシンボリックリンクを貼りたいということはよくあるかと思います。
しかし、普通に Shared Folders を設定しただけでは、クライアントOS上で以下のようなエラーが出てしまいます。

$ cd /media/sf_Documents/env
$ ln -s lib lib64
ln: failed to create symbolic link `lib64': Read-only file system

シンボリックリンク作成可能にする

ホストOS上で以下を実行します。
最後の 1 を忘れるとエラーになりますのでご注意を。

$ VBoxManage setextradata "${VMの名前}" VBoxInternal2/SharedFoldersEnableSymlinksCreate/${SharedFolderの名前} 1

きちんと設定されているか確認する

ホストOS上で以下を実行して、SharedFoldersEnableSymlinksCreate が設定されていれば次の起動からシンボリックリンクの作成が有効になっているはずです。

$ VBoxManage getextradata "${VMの名前}" enumerate           [~]
Key: GUI/LastCloseAction, Value: PowerOff
Key: GUI/LastGuestSizeHint, Value: 720,400
Key: GUI/LastNormalWindowPosition, Value: 583,142,960,432,max
Key: VBoxInternal2/SharedFoldersEnableSymlinksCreate/${SharedFolderの名前}, Value: 1

2016/01/12

Android Studio で Gradle wrapper をアップグレードする方法

Gradle wrapper は便利ですが、 場合によっては新しい機能を使うため等、Gradle のバージョンを上げなければならない時があります。
そんな時に Gradle wrapper で使用している Gradle のバージョンを上げる方法です基本的、ビルドが通っている間は無闇にビルドツールのバージョンを安易に変えるべきではありません

Gradle wrapper を使っていることを確認する

File → Settings → Build, Execution, Deployment → Build Tools → Gradle

Use default gradle wrapper (recommended) が選択されていることを確認します。

Gradle wrapper を更新する

プロジェクトのトップディレクトリで、以下を実行します。
2.8 のところは対象のバージョンに変更してください。

$ ${Android-studio}/gradle/gradle-2.8/bin/gradle wrapper --gradle-version 2.8

Android Studio でさらに更新する

この状態でプロジェクトのトップディレクトリ直下にある build.gradle を開きます。
すると、以下のようなメッセージが出るので、OK, apply suggestion! を押せばオーケーです

You can configure Gradle wrapper to use distribution with sources. It will provide IDE with Gradle API/DSL documentation.


2016/01/11

'defaultConfig' cannot be applied to '(Groovy.lang.Closure)' が出た時の対処方法

Android StudioAndroid Studio ver. 1.5.1 でタイトルのような Warning が出るようになってしまいました。

これは Android Studio を手作業で更新するなどして、指定した gradle のバージョンがインストールされていない場合android-studio ディレクトリ中に存在しない時に起こるようです。

修正方法としては、gradle wrapper を使うのが良さそうです。

File → Settings → Build, Execution, Deployment → Build Tools → Gradle

Use default gradle wrapper (recommended) を選択。

gradle wrapper を使いたくない場合は Use local gradle distribution を選択し、Gradle home のパスを正しく設定しなおしてもオーケーです。

2016/01/05

LD_LIBRARY_PATH を設定しても反映されないことがある

@ Ubuntu 14.04 LTS

システム全体でライブラリパスを指定したかったので、/etc/environment に以下を追記したのですが反映されませんでした他の環境変数は反映されているのに

/etc/environment
LD_LIBRARY_PATH=/usr/local/hoge/lib

LD_LIBRARY_PATH の設定が反映されないファイルがある

調べてみると、大分前からそういう仕様だそうです知らなかった…
システムワイドに LD_LIBRARY_PATH を指定することってあまり無いからなー


LD_LIBRARY_PATH についての説明から抜粋

Note: You can only set this environment variable inside an interactive shell. Since Ubuntu 9.04 Jaunty Jackalope, LD_LIBRARY_PATH cannot be set in $HOME/.profile, /etc/profile, nor /etc/environment files. You must use /etc/ld.so.conf.d/*.conf configuration files. See Launchpad bug #366728 for more information.

(意訳)
この環境変数はインタラクティブシェルの中でのみ設定できます。Ubuntu 9.04 以降では、LD_LIBRARY_PATH を $HOME/.profile, /etc/profile, /etc/environment の中で設定することは出来ません。 /etc/ld.so.conf.d/*.conf の中で設定する必要があります。さらに詳しい情報は Launchpad bug #366728 を参照してください。


というわけで、以下のファイルに LD_LIBRARY_PATH を記述しても反映されません。
  • $HOME/.profile
  • /etc/profile
  • /etc/environment

解決方法1: システム全体で使いたい場合

/etc/ld.so.conf.d 以下に新たなファイルを作り、各行にライブラリパスを追加します。

/etc/ld.so.conf.d/hoge.conf
# コメントも書けます
/usr/local/hoge/lib
/usr/local/hoge/share/lib

/etc/ld.so.cache を再構築するため、以下を実行します。

$ sudo ldconfig

ビルドしてみると、リンクが通るはずです。
ただ、この状態で LD_LIBRARY_PATH 環境変数を見てみても、先ほどのパスは追加されていません。
その代わり、以下のようにリンクされるライブラリのリストを取得することができます。

$ ldconfig -p
1418 libs found in cache `/etc/ld.so.cache'
        libzinnia.so.0 (libc6,x86-64) => /usr/lib/libzinnia.so.0
        libzephyr.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzephyr.so.4
        libzeitgeist-2.0.so.0 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzeitgeist-2.0.so.0
        libzeitgeist-1.0.so.1 (libc6,x86-64) => /usr/lib/libzeitgeist-1.0.so.1
        libz.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libz.so.1
        libz.so.1 (libc6) => /usr/lib32/libz.so.1
...

解決方法2: 特定ユーザの設定ファイルに記述する

特定のユーザだけが使いたいのであれば、 $HOME/.bashrc 等、インタラクティブシェルにのみ反映されるようなファイルに記述すればオーケーです。

2016/01/04

テキストモードで起動する for Ubuntu

たまにしか使わないので、備忘録としてこの方法はブートローダとして GRUB を使っている場合にのみ有効です

一度だけの場合

GRUB の画面が出てきたらカウントダウンが終わる前に e を押します。
GRUB の画面が出てこない場合は、起動直後に ESC を押すと出てくるはずそれでも出てこない場合は、ブートローダに GRUB を使っていない可能性があります

GRUB の画面
ここで e を押す

ブートコマンドの編集モードに入るので、quiet splashtext に変更します。

< linux      /boot/vmlinuz-x.xx.x-xx-generic root=UUID=yyyy ro  quiet splash $vt_handoff
---
> linux      /boot/vmlinuz-x.xx.x-xx-generic root=UUID=yyyy ro  text $vt_handoff

ブートコマンドの編集画面

構成によっては少々見づらいかもしれませんが、ゆっくり探すとあるはずです。
変更し終わったら、 Ctrl-x もしくは F11 でブートします。

この方法では、再起動すると元の設定に戻ります。
グラフィックボードのドライバをインストールする場合など、一時的に CUI にしたい場合に有用です。

永続的に変更する場合

設定ファイルを以下のように編集します。

/etc/default/grub
11c11
< GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
---
> GRUB_CMDLINE_LINUX_DEFAULT="text"

その後、update-grub をしないと設定が反映されないので注意これを忘れて何度も再起動したことがあります

$ sudo update-grub