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) getSystemService(WIFI_SERVICE); List wifiConfigurations = wm.getConfiguredNetworks(); 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 を大量に吐くようになってしまいました。