2016/10/30

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

Android エミュレーターで Read-only file system に書き込めるようにする で rootfs は揮発性だと書きました。 つまり、再起動すると加えた変更が元に戻ってしまうのです。

これは、rootfs が Initramfs という RAM 上に展開されるファイルシステムのためです。 RAM 上に展開されたファイルシステムに変更を加えても、その実体には変更が反映されないので 再起動後には消えてしまうというわけです。

この Initramfs、実体は cpio アーカイブを gzip 圧縮したものなので、以下のようにして永続的に変更を反映させることができます。

参考

第384回 Initramfsのしくみ:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

ramdisk.img の場所を確認

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

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

ramdisk.img の展開

空のディレクトリ1を作り、先ほど確認した ramdisk.img を展開します。

$ 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 ディレクトリにいることを確認し、以下のように圧縮します。

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

エミュレーターに登録

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

$ 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 を編集してもエミュレーターの起動時にデフォルト設定に書きなおされてしまいます2。 そのため、今回は ramdisk.img を直接変更する方法をとりました。

  1. 名前は何でも良いです 
  2. GUI の AVD Manager が原因かと疑ったのですが、コマンドラインから起動しても同じでした 
?
2016/10/29

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

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

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

マウント状態の確認

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

$ 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.zip1/system/media/bootanimation.zip で決まります。

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

参考

Deciphering Android's bootanimation.zip desc.txt

しかし、普通に zip ファイルを作るとうまく表示されません2。 この zip ファイル、無圧縮でなければいけないのです。

それに気づかず、数時間使ってしまったー3

無圧縮zip の作り方

Linux 系だと以下のように zip コマンドで作成します。

$ zip -0 bootanimation.zip desc.txt part0/* part1/*

実際にアニメーションを表示しているソースコード

バージョンによって微妙に挙動が変わっているので注意が必要です。

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

ブートアニメーションをいじろうという人は AOSP 等から自分でビルドする人かと思うので4、 コードは手元にあるという前提なのでしょう。

いろいろなブートアニメーション

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

Boot Animations « Droidboots

  1. Android 5.0 Lollipop 以降は /oem/media/bootanimation.zip に変更になった様子 
  2. 場合によっては起動すらしなくなっちゃうことも 
  3. 実は参考リンク先にも圧縮してはいけないと書いてありました 
  4. エミュレーターでも出来ますが、それやっても、あまり面白くないからなぁ 
?
2016/10/27

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

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

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

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

WifiInfo を使うコード

wpa_supplicant が提供する情報を取得する方法です。

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

1
2
3
4
5
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wm.getConnectionInfo();
 
WifiInfo.SupplicantState state = wifiInfo.getSupplicantState();

取得できる SupplicantState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

この 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 かどうか確認するという方式です。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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

1
2
3
4
5
6
7
8
public enum {
    CONNECTING,
    CONNECTED,
    SUSPENDED,
    DISCONNECTING,
    DISCONNECTED,
    UNKNOWN
}

取得できる DetailedState

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
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 になります2。 ただし、モバイル接続から Wi-Fi 接続に切り替わる瞬間、少しの間だけ 2 になることもあるようです3

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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 機能がハードウェア的に存在しない場合は、networkInfonull になります。

1
2
3
4
5
6
7
8
9
10
11
// 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 で設定する必要があります。

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

NetworkInfo を使うためのパーミッション

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

1
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  1. LTE等 
  2. 名前と挙動が合ってないし、あんまり使い勝手が良くないと思うのは私だけ? 
  3. 少なくとも、私の手元の Nexus 7 (2012) と Nexus 6P はそういう挙動でした 
?
2016/10/26

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

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

既に認証済みで接続可能な Wi-Fi Access Point の一覧を取得する方法です。

コード

1
2
3
4
5
6
7
8
9
10
11
12
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
 
List<WifiConfiguration> 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 で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">
2016/10/21

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

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

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

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

参考

Android Studio: "Update & Restart" doesn't work - Stack Overflow

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

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

  1. Ubuntu 16.04 LTS 
?
2016/10/20

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

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

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

BroadcastReceiver のコード

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 機能が現在有効かどうか調べる

登録方法1

AndroidManifest.xml に登録する方法

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

1
2
3
4
5
<receiver android:name=".WifiStateWatcher">
    <intent-filter>
        <action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
    </intent-filter>
</receiver>

登録方法2

コードで登録する方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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 実装に関する情報のまとめ の一部として書かれました

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

コード

1
2
3
4
5
6
7
8
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
 
// 有効にする
wm.setWifiEnabled(true);
 
// 無効にする
wm.setWifiEnabled(false);

パーミッション

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

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

現実的な実装

実際は、以下のように Wi-Fi の状態を確認してから使用することが多くなると思います1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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)
  1. 以下のコードを実行する場合は、android.permission.ACCESS_WIFI_STATE パーミッションも設定する必要があります 
?

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

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

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

コード

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

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

isWifiEnabled と wifiState との関係

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

1
2
3
public boolean isWifiEnabled() {
    return getWifiState() == WIFI_STATE_ENABLED;
}

GUI上での表示

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

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

  1. Android 7.0 Nougat 
?

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

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

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

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

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

コード

1
2
3
// 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 を大量に吐くようになってしまいました。