[Android] 開発者向けオプションをadbから使いこなせ

※本記事は「[開発者向けオプションを使いこなせ](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be)」をとても参考にさせてもらっています。
  執筆者の[@NUmeroDev](https://qiita.com/NUmeroDev)さん、ありがとうございます。

本記事では、「[開発者向けオプションを使いこなせ](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be)」で紹介されている開発者オプションを、adb経由から使う方法を紹介します。
各項目の説明については、元記事をご参照ください。

標準設定アプリが閉じられてしまっている環境や、自動テストなどのために使って貰えればと思います。

本記事の動作確認およびソースコードの確認は、Android 10で行っています。

## [スリープモードにしない](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%97%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AB%E3%81%97%E3%81%AA%E3%81%84)

```shell-session
# ON
$ adb shell settings put global stay_on_while_plugged_in 7

# OFF
$ adb shell settings put global stay_on_while_plugged_in 0
```

ちなみにONのときの7は、`BATTERY_PLUGGED_AC(1)`と`BATTERY_PLUGGED_USB(2)`と`BATTERY_PLUGGED_WIRELESS(4)`のORで、
「何らかの方法で充電しているとき」という意味です。

## [実行中のサービス](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E5%AE%9F%E8%A1%8C%E4%B8%AD%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9)

そのものズバリではないですが、
メモリ使用量の概要は、`dumpsys meminfo`で、
processとserviceの関係は`dumpsys activity processes`で確認することができます。

## [システム UI デモモード](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0-ui-%E3%83%87%E3%83%A2%E3%83%A2%E3%83%BC%E3%83%89)


システムUIデモモードを有効・表示するときは、adb経由だと少し複雑で、下記の流れになります。

```
# 「デモモードを有効にする」ON
adb shell settings put global sysui_demo_allowed 1

# systemuiにデモモード開始を通知
adb shell am broadcast -a com.android.systemui.demo --es command enter

# 時計の表示内容を設定
adb shell am broadcast -a com.android.systemui.demo --es command clock --es hhmm 1000

# ネットワーク状態の表示内容を設定
adb shell am broadcast -a com.android.systemui.demo --es command network \
  --es wifi show --es mobile show --es sims 1 \
  --es nosim false --es level 4 --es datatype lte
adb shell am broadcast -a com.android.systemui.demo --es command network --es fully true

# 電源状態の表示内容を設定
adb shell am broadcast -a com.android.systemui.demo --es command battery --es level 100 --es plugged false

# 各種アイコンを非表示に設定
adb shell am broadcast -a com.android.systemui.demo --es command status \
  --es volue hide --es bluetooth hide --es location hide --es alarm hide \
  --es zen hide --es sync hide --es tty hide --es eri hide --es mute hide \
  --es speakerphone hide --es managed_profile hide

# 通知アイコンを非表示に設定
adb shell am broadcast -a com.android.systemui.demo --es command notifications --es visible false

# 「デモモードを表示」ON
adb shell settings put global sysui_tuner_demo_on 1
```

システムUIデモモードを終了し、もとの表示に戻すときは、下記の流れです。

```
# systemuiにデモモード終了を通知
adb shell am broadcast -a com.android.systemui.demo --es command exit

# 「デモモードを表示」OFF
adb shell settings put global sysui_tuner_demo_on 0

# 「デモモードを有効にする」OFF
$ adb shell settings put global sysui_demo_allowed 0
```


デモモードの開始終了時の実装は、`frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java`にあります。
実装を読んでみると、実はデモモードで表示される時刻が、`Android Version % 24`だということがわかったりします。
※「[開発者向けオプションを使いこなせ](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0-ui-%E3%83%87%E3%83%A2%E3%83%A2%E3%83%BC%E3%83%89)」のスクリーンショットでは10時になっているので、Android O、ということですね。

intentのextra fieldに何を渡すかで、何を表示するかを変えることもできます。


## [クイック設定開発者用タイル](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E8%A8%AD%E5%AE%9A%E9%96%8B%E7%99%BA%E8%80%85%E7%94%A8%E3%82%BF%E3%82%A4%E3%83%AB)

```
# 「レイアウト境界を表示」をクイック設定タイルに表示
adb shell pm enable 'com.android.settings/.development.qstile.DevelopmentTiles\$ShowLayout'
adb shell cmd statusbar add-tile 'com.android.settings/.development.qstile.DevelopmentTiles\$ShowLayout'

# 「レイアウト境界を表示」をON
adb shell cmd statusbar click-tile 'com.android.settings/.development.qstile.DevelopmentTiles\$ShowLayout'

# 「レイアウト境界を表示」をクイック設定タイルから削除表示
adb shell cmd statusbar add-tile 'com.android.settings/.development.qstile.DevelopmentTiles\$ShowLayout'
adb shell pm enable 'com.android.settings/.development.qstile.DevelopmentTiles\$ShowLayout'
```

その他の項目に対応するComponentNameは以下の通りです。

| Settings項目 | ComponentName |
|-----------------|------------------|
| レイアウト境界を表示 | ShowLayout |
| HWUIレンダリングのプロファイル作成 | GPUProfiling |
| RTLレイアウト方向を使用 | ForceRTL |
| ウィンドウアニメスケール | AnimationSpeed |
| ウィンスコープトレース | WinscopeTrace |
| センサーOFF | SensorsOff |


## [タップを表示](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%82%BF%E3%83%83%E3%83%97%E3%82%92%E8%A1%A8%E7%A4%BA)

```
# ON
adb shell settings put system show_touches 1

# OFF
adb shell settings put system show_touches 0
```


## [レイアウトの境界を表示](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%81%AE%E5%A2%83%E7%95%8C%E3%82%92%E8%A1%A8%E7%A4%BA)

```shell-session
# ON
adb shell setprop debug.layout true
adb shell am restart

# OFF
adb shell setprop debug.layout false
adb shell am restart
```
setpropしたあとで立ち上げたactivityに対して、レイアウトの境界が表示されます。
そのため、system UIなども含めて設定が反映されるよう、`am restart`を呼んでいます。
開発者メニューから変更した場合は、`ViewRootImpl#invalidateWorld()`を呼び出すことで即時反映しているようですが、
同等の処理をCUIから呼び出す方法は見つかりませんでした。

クイック設定パネルが使える環境であれば、前述の`cmd statusbar click-tile`でも同じことが`am restart`なしで実現できます。

## [最小幅](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E6%9C%80%E5%B0%8F%E5%B9%85)

```
# 最小幅を320dpにする
adb shell wm size 320dpx640dp

# 最小幅をデフォルトに戻す
adb shell wm size reset
```

端末の最小幅は、`wm size`コマンドで変更ができます。
GUIとは違い、幅だけでなく高さも指定する必要があります。
`wm size`を引数なしで実行することで、縦横のピクセル数がわかるので、縦横比を合わせて高さも指定してあげましょう。

また、GUIとは違って320dp以上といった制約もないので、極端に小さな値を試すことも可能です。
（そのような端末はないことになっているので、試す必要もないはずですが）

## [ディスプレイ カットアウト](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%83%87%E3%82%A3%E3%82%B9%E3%83%97%E3%83%AC%E3%82%A4-%E3%82%AB%E3%83%83%E3%83%88%E3%82%A2%E3%82%A6%E3%83%88)

```
# 右上ノッチ
adb shell cmd overlay enable-exclusive com.android.internal.display.cutout.emulation.corner

# 上下中央ノッチ
adb shell cmd overlay enable-exclusive com.android.internal.display.cutout.emulation.double

# 上部中央ノッチ
adb shell cmd overlay enable-exclusive com.android.internal.display.cutout.emulation.tall

# デフォルトに戻す
# adb shell cmd listで有効になっているpackage名を確認し、無効にする
adb shell cmd disable com.android.internal.display.cutout.emulation.tall
```

## [GPUオーバードローをデバッグ](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#gpu%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%89%E3%83%AD%E3%83%BC%E3%82%92%E3%83%87%E3%83%90%E3%83%83%E3%82%B0)

```shell-session
# オーバードロー領域を表示
adb shell setprop debug.hwui.overdraw show

# 非表示
adb shell getprop debug.hwui.overdraw false
```

## [フォースダークのオーバーライド](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B9%E3%83%80%E3%83%BC%E3%82%AF%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89)

```
# ON
adb shell setprop debug.hwui.force_dark true

# OFF
adb shell setprop debug.hwui.force_dark false
```
 

## [色空間シミュレート](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E8%89%B2%E7%A9%BA%E9%96%93%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88)

```shell-session
# 色空間シミュレートを有効にする
adb shell settings put secure accessibility_display_daltonizer_enabled 1

# Monochromacy (1色覚)
adb shell settings put secure accessibility_display_daltonizer 0

# Protanomaly (1型3色覚)
adb shell settings put secure accessibility_display_daltonizer 1

# Deuteranomaly (2型3色覚)
adb shell settings put secure accessibility_display_daltonizer 2

# Tritanomaly (3型3色覚)
adb shell settings put secure accessibility_display_daltonizer 3

# 色空間シミュレートを無効にする
adb shell settings put secure accessibility_display_daltonizer_enabled 0
```


## [アクティビティを保持しない](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be#%E3%82%A2%E3%82%AF%E3%83%86%E3%82%A3%E3%83%93%E3%83%86%E3%82%A3%E3%82%92%E4%BF%9D%E6%8C%81%E3%81%97%E3%81%AA%E3%81%84)

```shell-session
# ON
adb shell settings put global always_finish_activities 1
adb shell am restart

# OFF
adb shell settings put global always_finish_activities 0
adb shell am restart
```


# adbからの実行方法の調べ方
上記以外の開発者オプションをadbから変更したくなったときのために、上記を調べた方法も記載しておきます。

## 1. SettingsService

`dumpsys settings`にて、SettingsServiceが保持する現時点の全項目の設定値と、変更履歴を確認できます。
Settingsアプリによって変更される設定値は、ここに保存されることが多いです。

一度実際に変えてみて、変更履歴を確認することで、簡単に対応するkeyの名前を確認できます。

また、このとき対象の設定項目のNAMESPACEも確認することができます。
例えば、下記の出力例でいうと、`stay_on_while_plugged_in`は"GLOBAL SETTINGS"の中にあるので、
NAMESPACEが"GLOBAL"だとわかります。

```shell-session
$ adb shell dumpsys settings | grep -v '^_id'
CONFIG SETTINGS (user 0)

Historical operations
1970-01-01 04:02:37 persist
1970-01-01 04:02:37 update intelligence_bubbles/intelligence_bubbles_webref_superpacks_manifest_version


GLOBAL SETTINGS (user 0)

Historical operations
1970-01-01 05:53:17 persist
1970-01-01 05:53:16 update stay_on_while_plugged_in


SECURE SETTINGS (user 0)

Historical operations
1970-01-01 02:23:07 persist
1970-01-01 02:23:07 update zen_settings_updated


SYSTEM SETTINGS (user 0)

Historical operations
1970-01-01 00:53:40 persist
1970-01-01 00:53:39 update screen_off_timeout
```

keyがわかれば、実際に設定を変化させて、`dumpsys settings`で値を確認することができます。
`value:`に表示されているのが、設定値です。

```shell-session
# 「スリープモードにしない」を無効にしているとき
$ adb shell dumpsys settings | grep '^_id' | grep stay_on_while_plugged_in
_id:210 name:stay_on_while_plugged_in pkg:com.android.settings value:0 default:0 defaultSystemSet:true

# 「スリープモードにしない」を有効にしているとき
$ adb shell dumpsys settings | grep '^_id' | grep stay_on_while_plugged_in
_id:211 name:stay_on_while_plugged_in pkg:com.android.settings value:7 default:7 defaultSystemSet:true
```

NAMESPACE, key, valueがわかれば、adbから設定値を更新してみましょう。

```
$ adb shell settings put global stay_on_while_plugged_in 7
$ adb shell settings put global stay_on_while_plugged_in 0
```

## 2. Intent

設定アプリは設定値の変更だけでなくIntentを投げている可能性があります。（例：「システムUIデモモード」）

`dumpsys activity broadcasts`で送出されるIntentのactionを確認して、実装を確認するとよいです。

## 3. System Property

System Propertyを変更している場合もあります。（例：「レイアウトの境界を表示」）

## 4. 実装を確認する

こう言っては元も子もないですが、結局Settingsアプリをどう実装するかで、設定項目の保存先はいかようにもできてしまいます。

1. 2. 3.を見ても変化が見つからない場合は、さっさと実装を見たほうが早いです。
デバッグ機能を有効にしているだけなので、そんなに複雑な実装ではないはずです。

「最小幅」の場合は、Settingsアプリのレイアウトで対応する要素が`DensityPreference`という独自実装になっており、
その中で`WindowManager`に処理を委譲していました。

```xml:packages/apps/Settings/res/xml/development_settings.xml
        <com.android.settings.display.DensityPreference
            android:key="density"
            android:title="@string/developer_smallest_width" />
```

```java:packages/apps/Settings/src/com/android/settings/display/DensityPreference.java
    @Override
    protected void onDialogClosed(boolean positiveResult) {
        if (positiveResult) {
            try {
                final Resources res = getContext().getResources();
                final DisplayMetrics metrics = res.getDisplayMetrics();
                final int newSwDp = Math.max(Integer.parseInt(getText()), 320);
                final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels);
                final int newDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / newSwDp;
                final int densityDpi = Math.max(newDensity, 120);
                DisplayDensityUtils.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, densityDpi);
            } catch (Exception e) {
		// TODO: display a message instead of silently failing.                               
		Slog.e(TAG, "Couldn't save density", e);
            }
	}
    }
```

```java:frameworks/base/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
    public static void setForcedDisplayDensity(final int displayId, final int density) {
        final int userId = UserHandle.myUserId();
        AsyncTask.execute(() -> {
            try {
                final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
                wm.setForcedDisplayDensityForUser(displayId, density, userId);
            } catch (RemoteException exc) {
                Log.w(LOG_TAG, "Unable to save forced display density setting");
            }
        });
    }
```

# 参考
- [開発者向けオプションを使いこなせ](https://qiita.com/NUmeroDev/items/862e45d78727ff75e2be)
- [コマンドラインからIntentをbroadcastする](https://qiita.com/imanishisatoshi/items/1127730a251c6abf393d)
- [【Android】いまさら聞けないdp入門](https://qiita.com/nein37/items/0a92556a80c6c14503b2)
- [日本医学界 医学用語辞典 色覚関連用語について](http://jams.med.or.jp/dic/colorvision.html)