Roman Nurik(github:romannurik)さんのenvリポジトリにAndroidのコマンドに関する知見があった話

Android Asset Studioとかを作ったGoogleのRoman Nurikさん。

Roman Nurik(github:romannurik)さんのenvってリポジトリを眺めてたらAndroidに関するコマンドがいくつかあった。

その中で「これは使えるかもなー」と思ったものを紹介。

紹介したもの以外でもためになりそう or 知見!!っぽいのはあるので見てみるといいよー

github.com

android-resize - 端末の解像度を変更する adb shell wm sizeのShell Script

adb shell wm sizeはとりあえず端末の解像度を変更できるすげーもん!だと思ってもらえればいい。

確かAndroid 4.3くらいから使えるやつだった気がするなーどうだろー。内部ではWindowManagerゴニョゴニョしてるだけ。

以下のQiitaとか見てもらえればいいかな。

qiita.com

んで、そのadb shell wm sizeのラッパー的なものがandroid-resizeってShell Scriptで書いてあった。

中身は以下のような感じ。

#!/bin/sh
if [[ ("$1" != "4" && "$1" != "5" && "$1" != "7" && "$1" != "reset") ]]; then
  echo "Usage: `basename $0` <size (4|5|7|reset)> <optional orientation (land|port)>" >&2
  exit
fi

adb root

PREFIX="adb shell wm size"
#PREFIX="adb shell am display-size"

case "$1" in
  4)
    W=1280
    H=768
    ;;
  5)
    W=1280
    H=720
    ;;
  7)
    W=1920
    H=1200
    ;;
  reset)
    COMMAND="$PREFIX reset"
esac

if [ -z "$COMMAND" ]; then
  case "$2" in
    port)
      COMMAND="$PREFIX ${H}x${W}"
      ;;
    *)
      COMMAND="$PREFIX ${W}x${H}"
      ;;
  esac
fi

echo $COMMAND
$COMMAND

env/android-resize at master · romannurik/env · GitHub

数字の割り当てや解像度の数値は自分に合った感じでカスタマイズすれば使えそう!

ちなみに、adb rootがあるのが気になるけど、使うならこれは消しておいた方がいいね...

android-screen-gif - screenrecordの結果をanimation gifに変換してくれるShell Script

Android 4.4から追加されてるscreenrecordを使って、その結果のmp4をanimation gifに変換するものがandroid-screen-gifってShell Scriptで書いてあった。

android-screen-gifって打って、端末で録画したい操作をして、終わりにしたかったらターミナルでなんかキーを打つと録画した内容をanimation gifにしてくれるすぐれもの。

中身は以下のような感じ。

#!/bin/bash
NAME=screencap
if [[ "$1" != "" ]]; then
  NAME="$1"
fi
adb shell screenrecord --bit-rate=20M "/sdcard/$NAME.mp4" &
PID=$!
echo $PID
read -n1 -r -p "Press any key to stop" key
kill -SIGHUP $PID
sleep 0.2
adb pull "/sdcard/$NAME.mp4" && gifify -np 360:640 -r 16 -o "$NAME" "$NAME.mp4"
adb shell rm "/sdcard/$NAME.mp4"

env/android-screen-gif at master · romannurik/env · GitHub

mp4をanimation gifに変換するためにgififyというものを使ってるけど、多分以下を使ってるんじゃないかなーと推測。

github.com

gififyのオプションがそのままだと動かなかったので以下のように-npオプションを-pオプションに変えたらうまく動いた。

adb pull "/sdcard/$NAME.mp4" && gifify -p 360:640 -r 16 -o "$NAME" "$NAME.mp4"

あと、sleepが0.2だとうまく動かなかったのでsleep 1に変えたらうまく動いた。それでもさらにダメそうだったらここのsleep伸ばしてみるといいかも。

これと似たことができるGUI Toolを使っていつもやってたけど、こっちの方がターミナルでできるに楽かもなー。

まとめ

adb厨、adb shell厨な私としてはこーゆーのすげー好き!!

Google Compute Engine上でSlackのBotkitを動かすぞい!

Google Compute Engine(GCE)でSlackのBotkitを動かすぞい!という話。

以降 Google Compute EngineはGCEと書きます!

Slackのbotkitの詳細については割愛しますが、全く動かしたことないです!って人は一度以下とか読んで簡単なものでも動かしておくといいかもです。

toach.click

Google Compute EngineのVM インスタンスを作成する

今回はLinux VMインスタンスを作成します。

公式のドキュメントにそってやれば簡単にインスタンスは作れます。ちなみに、私は操作に慣れているUbuntuを使ってます。

Quickstart Using a Linux VM  |  Compute Engine Documentation  |  Google Cloud Platform

インスタンス作るまでの手順は割愛しますが、公式のドキュメントより細かく手順が欲しい!!って方は以下のQiitaとかがオススメです。

qiita.com

マシンタイプは「f1-micro(vCPU x 1、メモリ 0.6 GB)」とかで一番安いのでいいでしょう。

VM インスタンスSSHで入ってnodeをインストールする

gcloudとか使ってターミナルから操作もできますが、そこまで長い作業にならないのでWebのSSHで作業します。

VMインスタンスの一覧画面で、作業したいインスタンスSSHから以下のように「ブラウザ ウィンドウで開く」を選択すれば、WebのSSHが開きます。

f:id:operando:20160925204116p:plain

んで、開いたらVM インスタンスにnodeをインストールします。

nodeのサイトに各メジャーOSでnodeをインストールする方法が書いてありますので、作成したVMインスタンスのOSに合わせてコマンドを見つける。

Installing Node.js via package manager | Node.js

私が普段VMインスタンスで使うOSはUbuntuなので以下のコマンドを実行すればnodeがインストールできます。npmもね。

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

インストールが終わったら一応nodeとnpmのバージョンを確認してインストールされていることとか確認しておくといいですね。

VMインスタンス上で動かすbotkitのサンプルをcloneする

今回使ったのはSlackのBotkitでbotにメンション付きで発言すると、内容をオウム返しするbotさんを動かします!

github.com

上記のRepositoryを以下のようにcloneします。ついでにnpm installもして実行に必要なモジュールもインストールしておく。

git clone https://github.com/operando/slack-botkit-sample.git
cd slack-botkit-sample
npm install

別途自身で作成したbotがあればそちらを使うでもいいですねー。

ちなみに、Ubuntuは元からgitが入っているので追加で入れる必要はないですが、立ち上げるOSによっては別途追加が必要かもです。

VMインスタンス上でbotkitを動かすぞい!!

VMインスタンス上で動かすbotが用意できたら、あとは実行するだけです。

私が作ったbotなら以下のように実行します。

// your_api_tokenのところに自身のSlackのAPI tokenを入れる
 token=your_api_token node parrot-bot.js

起動が確認できたらbotが動いてるSlackに移動して動作確認してみます。

動いた!!

やたー!!動いたー!!

f:id:operando:20160925211307g:plain

後処理

動いた!!と満足してもうVMインスタンス不要だったら停止するなり削除するなりしておくといいかと。

まとめ

GCEはインスタンスの作成も操作も簡単なのでいいですねー

Android Nのbugreportが進化してた話

普通のアプリからbugreportの取得を行うことができるのかどうか調べていたら、Android Nからbugreportがちょっと進化していることがわかった。

bugreportってなに??って人は adb shellで adb shell bugreportとかしてみればわかるよ。雑に言うとdebugのために端末の情報をdumpするみたいなー。

内部的な話は今コード読んでるので、なんとなくわかってきたら別途まとめようかなー

取得できるbugreportの形式が増えてる

開発者向けオプションの「バグレポートを取得」を押すと、以下のように取得するbugreportの形式を選べ!ってダイアログが出てくるようになった。

f:id:operando:20160924215902p:plain

具体的に各形式で取得できるbugreportがどんなものなのかまでは詳しくみてない。

ちなみにコード読んだ感じ、形式は上の画像 + リモートから取得するbugreportの形式が用意されてるっぽい。

電源メニューからバグレポート取得のメニューをロングタップすることで取得できるようになってる

開発者向けオプションの「バグレポートのショートカット」を有効にすると、電源メニューにバグレポート取得のメニューが増える。

これは前からあったね。

f:id:operando:20160924215720p:plain

んで、実際に電源メニューを開いてバグレポート取得のメニューをロングタップすると、bugreportの取得が始まる。これはどうやらNから追加されたみたい。

わかりにくいけど以下のような感じ。

f:id:operando:20160924220621g:plain

ちなみにコード読んだ感じ、ロングタップからの取得だと上で出てたbugreportの形式的には、「完全レポート」の形式のbugreportになる。

電源メニューからバグレポート取得のメニューをクリックするとすぐにbugreportの取得が始まる

わかりにくけど以下のような感じ。

f:id:operando:20160924221139g:plain

ちなみにコード読んだ感じ、クリックからの取得だと上で出てたbugreportの形式的には、「対話型レポート」の形式のbugreportになる。

Android Mまではメニューをクリックするを以下のようなダイアログが出てたけど、出なくなったのはうれしいかなー

f:id:operando:20160924221425p:plain

まとめ

Android Nからbugreportが進化している。取得するbugreportに形式が増えたのは興味深い。

どうやらNからならDevicePolicyManager経由で取得もできるかもしれないのでそこら辺も後々調べてみよう

あとbugreport取得したらlogcatのeventにlogを落ちるようになってるのがわかって、log周りも色々増えてることがわかったので知見だ!

TextInputLayoutのpassword visibility toggleを試してみた

Android support library 24.2.0からTextInputLayoutに追加されたpassword visibility toggleを試してみた。

試したサンプルコードはGithubにあげてあります。

github.com

password visibility toggleってなんぞ??

簡単に説明するとパスワード入力する際に、入力している内容をtoggleで表示・非表示と切り替えるやーつーですね(雑だな...

Material Designのドキュメントに詳しく書いてあるので、それを見れば「あーこれかー」となるはず。

Text fields - Components - Material design guidelines

んで、今までこれと似たようなものを実装したことがある人ならわかるだろうけど、今まで独自の方法で実装するしかなかったわけだ。

その実装がsupport library 24.2.0からTextInputLayoutに導入されたってこと。うれしいです☺

さようなら、独自実装...😇

TextInputLayoutでどのようにpassword visibility toggleを使うか

基本はすごく簡単です。

以下のような感じでTextInputLayoutの子ViewのEditTextにandroid:inputType="textPassword"を指定するだけで、デフォルトのpassword visibility toggleが出てきます。

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="textPassword"
        android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>

こんな感じで動きます!簡単ですね!!

f:id:operando:20160923215819g:plain

もしpassword visibility toggleを出したくなければ、TextInputLayoutにapp:passwordToggleEnabled="false"を追加すると出なくまります。

inputTypeのxxxPasswordすべて試してみた

以下のような感じでinputTypeのxxxPasswordすべて試してみます。(長いのでlayout_widthとlayout_height省いてます

<android.support.design.widget.TextInputLayout ... >

    <EditText
        android:hint="textPassword"
        android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout ... >

    <EditText
        android:hint="numberPassword"
        android:inputType="numberPassword" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout ... >

    <EditText
        android:hint="textVisiblePassword"
        android:inputType="textVisiblePassword" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout ... >

    <EditText
        android:hint="textWebPassword"
        android:inputType="textWebPassword" />
</android.support.design.widget.TextInputLayout>

hintに出ている値が各EditTextのinputTypeに設定されています。

f:id:operando:20160923220303g:plain

android:inputType="textVisiblePassword"を設定しているものはpassword visibility toggleが出てないですね。当たり前かー。

passwordToggleDrawableでアイコンを変更する

デフォルトの目のアイコンはpasswordToggleDrawable属性で変えられます。

サンプルコードではVectorのドロイド君にアイコンを変更してます(デフォルトの目のアイコンもVectorです

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:passwordToggleDrawable="@drawable/ic_android_black_24dp">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="passwordToggleDrawable"
        android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>

変更すると以下の感じでアイコンが変わります。

f:id:operando:20160923222102g:plain

XMLの属性だけじゃなくてメソッドもちゃんとありますよ

今回のサンプルコードでは全てXMLの属性で指定してますが、ちゃんと属性とマッチしてるメソッドも用意されてます。

詳しくはTextInputLayoutのドキュメントを見てみてください。

developer.android.com

今回は使用しませんでしたが属性も他にpasswordToggleTintpasswordToggleTintModeがあります。

自身のアプリのデザインに合わせてカスタマイズすると良さそうですね。

まとめ

デフォルトのデザインでいいのならTextInputLayoutでpassword visibility toggleを試すのはすごく簡単だったなー

support libraryでこういうのが増えてくれるのは独自で実装する部分が減るのでとても助かりますねー

ANDROID IDがどのように生成されているかざっくり調べた

悪名高い??ANDROID IDがどのように生成されているのか気になったのでざっくり調べた。

悪名高いと言われる闇の話は置いておいて....とりあえず生成方法だけざっくり調べた。

どのような手順で生成されるのか?という疑問はバージョンによって実装が異なるっぽかったので調べない。

ANDROID IDとは

ドキュメント見てもらえばわかるけどざっくり以下のようなもの。

  • ランダムに生成される64ビットの数値を16進数文字列にしたもの
  • 端末の最初の設定時に生成される。その後、ファクトリーリセットするまではその時に生成された値が常に取得できる
  • ファクトリーリセットすることで値は再生成される
  • 4.2以上でmultiple usersが導入され、ユーザごとにANDROID IDが生成される

developer.android.com

ANDROID IDは以下のような感じで簡単に取得できる。

String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

ANDROID IDを使用する際の気をつけていること

ざっくり以下かなー他にもなにかあれば教えてほしいかなー

  • 「ランダムに生成される64ビットの数値を16進数文字列にしたもの」なので文字数は固定ではない
  • どんなアプリでも同じ値がAPIを通して取得できる
  • ファクトリーリセットすることで値が変わる
  • 同じANDROID IDを持った端末が存在する可能性がある
  • multiple users環境ではユーザごとにANDROID IDが生成されるため、ANDROID IDは端末に1つではない

ANDROID IDがどのように生成されているのか

とりあえずAndroid Nのコードから見てみる。

ANDROID IDを生成しているのはSettingsProvider.javaensureSecureSettingAndroidIdSetLockedメソッドだね。

全体の処理は以下のような感じ。詳しくは見ないけどドキュメントどおりmultiple usersの想定もされてる実装になってる。

private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) {
    Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID);

    if (!value.isNull()) {
        return;
    }

    final int userId = getUserIdFromKey(secureSettings.mKey);

    final UserInfo user;
    final long identity = Binder.clearCallingIdentity();
    try {
        user = mUserManager.getUserInfo(userId);
    } finally {
        Binder.restoreCallingIdentity(identity);
    }
    if (user == null) {
        // Can happen due to races when deleting users - treat as benign.
        return;
    }

    String androidId = Long.toHexString(new SecureRandom().nextLong());
    secureSettings.insertSettingLocked(Settings.Secure.ANDROID_ID, androidId,
            SettingsState.SYSTEM_PACKAGE_NAME);

    Slog.d(LOG_TAG, "Generated and saved new ANDROID_ID [" + androidId
            + "] for user " + userId);

    // Write a drop box entry if it's a restricted profile
    if (user.isRestricted()) {
        DropBoxManager dbm = (DropBoxManager) getContext().getSystemService(
                Context.DROPBOX_SERVICE);
        if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) {
            dbm.addText(DROPBOX_TAG_USERLOG, System.currentTimeMillis()
                    + "," + DROPBOX_TAG_USERLOG + "," + androidId + "\n");
        }
    }
}

http://tools.oesf.biz/android-7.0.0_r1.0/xref/frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java#1915

ANDROID IDの生成処理は以下のような感じで実装されてる。

String androidId = Long.toHexString(new SecureRandom().nextLong());

SecureRandomでランダムなlong値を生成して、それをLong.toHexStringで16進数表現の文字列にする。

それがANDROID IDってことになるね。

ちなみにSecureRandom.nextLongの実装はRandom.nextLong)なんだけど、ドキュメントと実装見ればわかるけど、long 値の一部しか返さないっぽいんだよね。

なのでlong値の幅 -9223372036854775808~9223372036854775807の中の一部ってことだね。うーん...思ってた以上に範囲が狭い気もする...

あと、SecureRandom.nextLong生成される値によって16進数文字列にした時の文字列の長さは変わる

雑に以下のようなやつを実行してみればわかるけど、文字列の長さが16だったり、15だったり、14だったりする。

for (int i = 0; i < 100000; i++) {
    String androidId = Long.toHexString(new SecureRandom().nextLong());
    System.out.println(androidId.length());
}

これが最初の気にしておかなければいけない点で書いた「ランダムに生成される64ビットの数値を16進数文字列にしたもの」なので文字数は固定ではないってこと。

他のAndroidのバージョンではどう実装されているか

全部のバージョン見るのは面倒なので、いくつかのバージョンだけ。

4.0.1ではensureAndroidIdIsSetメソッドでANDROID IDが生成されてる。

もちろんSecureRandom.nextLong + Long.toHexStringANDROID IDが生成されてる。

てかSlog.dじゃなくて普通のLog.d使ってる...まあいいや...

private boolean ensureAndroidIdIsSet() {
    final Cursor c = query(Settings.Secure.CONTENT_URI,
            new String[] { Settings.NameValueTable.VALUE },
            Settings.NameValueTable.NAME + "=?",
            new String[] { Settings.Secure.ANDROID_ID }, null);
    try {
        final String value = c.moveToNext() ? c.getString(0) : null;
        if (value == null) {
            final SecureRandom random = new SecureRandom();
            final String newAndroidIdValue = Long.toHexString(random.nextLong());
            Log.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue + "]");
            final ContentValues values = new ContentValues();
            values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
            values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
            final Uri uri = insert(Settings.Secure.CONTENT_URI, values);
            if (uri == null) {
                return false;
            }
        }
        return true;
    } finally {
        c.close();
    }
}

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java#316

ちなみに、コードは割愛しますが2.3.7の実装はだいたい4.0.1と同じ。もちろんSecureRandom.nextLong + Long.toHexStringANDROID IDが生成されてる。

http://tools.oesf.biz/android-2.3.7_r1.0/xref/frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java#322

その他のバージョンもざっくり見たけど、SecureRandom.nextLong + Long.toHexStringANDROID IDが生成されていることは変わらない。当たり前か。

まとめ

Long.toHexString(new SecureRandom().nextLong())で生成されるものがANDROID IDだねーということ

思ってたよりANDROID IDの生成処理があっさりしたもので驚きだったかなー

モヤモヤするのはドキュメントに書いてある「 ランダムに生成される64ビットの数値を16進数文字列にしたもの」というのは表面上はあっているが、内部処理の話をすると「えー」と言いたくなる気分...

気になったら調べてみるもんだなー