言いたいことはそれだけか

KotlinとかAndroidとかが好きです。調べたことをメモします。٩( 'ω' )و

開発者オプションメニューの値は3rd partyのアプリから書き換えるのが難しい

[2016.10.19 追記しました]

前回の続きです。
Quick Settings にTileを表示するところまで実装したので、次はそのTileをタップした時に何か便利な挙動をするのを目指してみます。

具体的には下記の二つどちらかできたら便利だなぁと思って調べてみました。
どちらもできませんでした:;(∩´﹏`∩);:
[↑できる方法があったので追記してます]

  1. USB debuggingのON/OFF
  2. 「Activityを保持しない」の設定のON/OFF

それではそれぞれなぜダメだったのか、調べた結果を書き残します。

USB debuggingのON/OFF

まず、開発者オプションの画面のコードを追っていきます。 それぞれの項目のlabelのstring resourceとかから適当にOpen Grokを検索すると、 DevelopmentSettings.java にたどり着きます。
で、ここのON/OFFをどうやってとっているかというと、こんな感じで Settings.Global.ADB_ENABLED の値を見ています。

   642     private void updateAllOptions() {
   643         final Context context = getActivity();
   644         final ContentResolver cr = context.getContentResolver();

(省略)


    646         updateSwitchPreference(mEnableAdb, Settings.Global.getInt(cr,
    647                 Settings.Global.ADB_ENABLED, 0) != 0);

長くなるので省略しますが、この Settings.Global.getInt()content://settings/global のcontent providerから値を取得しようとします。

   1428     public static final String AUTHORITY = "settings";


   6524     public static final class Global extends NameValueTable {
   6525         /**
   6526          * The content:// style URL for global secure settings items.  Not public.
   6527          */
   6528         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");

SettingsProvider.java の中で、getの方は問題ないのですが、updateの方はpermission checkが入っています。

    754     private Setting getGlobalSetting(String name) {
    755         if (DEBUG) {
    756             Slog.v(LOG_TAG, "getGlobalSetting(" + name + ")");
    757         }
    758 
    759         // Get the value.
    760         synchronized (mLock) {
    761             return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL,
    762                     UserHandle.USER_SYSTEM, name);
    763         }
    764     }


    766     private boolean updateGlobalSetting(String name, String value, int requestingUserId,
    767             boolean forceNotify) {
    768         if (DEBUG) {
    769             Slog.v(LOG_TAG, "updateGlobalSetting(" + name + ", " + value + ")");
    770         }
    771         return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE,
    772                 forceNotify);
    773     }

    792     private boolean mutateGlobalSetting(String name, String value, int requestingUserId,
    793             int operation, boolean forceNotify) {
    794         // Make sure the caller can change the settings - treated as secure.
    795         enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);

書き込みを行うにはWRITE_SECURE_SETTINGS が必要なのですが、こちらは残念ながら3rd partyのアプリは設定できないようになっています。

developer.android.com

このpermissionがない場合、Security Exceptionで落ちます。。。

   1305     private void enforceWritePermission(String permission) {
   1306         if (getContext().checkCallingOrSelfPermission(permission)
   1307                 != PackageManager.PERMISSION_GRANTED) {
   1308             throw new SecurityException("Permission denial: writing to settings requires:"
   1309                     + permission);
   1310         }
   1311     }

「Activityを保持しない」の設定のON/OFF

USB debuggingと同様にDevelopmentSettings.javaを見ていきましょう。
「Activityを保持しない」のpreference keyは IMMEDIATELY_DESTROY_ACTIVITIES_KEY です。ちょっとアグレッシブな名前です。

読み込みの方はUSB debuggingと同様に Settings.Global に定義してある値を読みに行きます。

   1578     private void updateImmediatelyDestroyActivitiesOptions() {
   1579         updateSwitchPreference(mImmediatelyDestroyActivities, Settings.Global.getInt(
   1580                 getActivity().getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0);
   1581     }

書き込みの方はちょっと挙動が変わります。

   1570     private void writeImmediatelyDestroyActivitiesOptions() {
   1571         try {
   1572             ActivityManagerNative.getDefault().setAlwaysFinish(
   1573                     mImmediatelyDestroyActivities.isChecked());
   1574         } catch (RemoteException ex) {
   1575         }
   1576     }

こんな感じでActivityManagerNativeを操作しています。ActivityManagerNative 自体は@hideなAPIなので残念ながら3rd partyのアプリでは使えません。
ActivityManagerService経由で操作する方法もありますが、こちらを経由するにしてもpermissionが必要です。

   11940     @Override
   11941     public void setAlwaysFinish(boolean enabled) {
   11942         enforceCallingPermission(android.Manifest.permission.SET_ALWAYS_FINISH,
   11943                 "setAlwaysFinish()");
   11944 
   11945         long ident = Binder.clearCallingIdentity();
   11946         try {
   11947             Settings.Global.putInt(
   11948                     mContext.getContentResolver(),
   11949                     Settings.Global.ALWAYS_FINISH_ACTIVITIES, enabled ? 1 : 0);
   11950 
   11951             synchronized (this) {
   11952                 mAlwaysFinishActivities = enabled;
   11953             }
   11954         } finally {
   11955             Binder.restoreCallingIdentity(ident);
   11956         }
   11957     }

SET_ALWAYS_FINISH のpermissionもWRITE_SECURE_SETTINGSと同様に3rd partyのアプリでは設定できません。

developer.android.com

というわけでどちらも3rd partyのアプリでは書き換えられないというお話でした(´・_・`)


[2016.10.19 追記]
GoogleのNick Butcherが同じことをやっていて、彼はadb commandで手動でpermissionをgrantする方法で解決していました。
この方法で解決することは手元でも確認済みです。すげえ!٩( 'ω' )و

github.com