Quick SettingsにCustom Tileを追加する
Android N からQuick Settingsに3rd Partyが好きな物を置けるようになりました。 これを使うと簡単にデバッグツール作れるのでは?と思ってやってみました٩( 'ω' )و
右下のやつ
やりたかったこと
- Quick SettingsにTileを追加する
- Tileをタップした時に何か便利な挙動をする
長くなりそうなので、今回は1のみ扱います。
Quick SettingsにTileを追加する
TileServiceクラスを拡張したサービスを作ったら瞬殺です。 まずはAndroidManifestにサービスを追加してpermissionとintent filterを設定しましょう
<service android:name=".DebugTileService" android:label="@string/app_name" android:icon="@drawable/ic_star_black_24dp" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" > <intent-filter> <action android:name="android.service.quicksettings.action.QS_TILE" /> </intent-filter> </service>
あとはこのサービス内でclick eventのハンドリングをしましょう。
class DebugTileService : TileService() { override fun onClick() { super.onClick() when (qsTile.state) { Tile.STATE_ACTIVE -> { // do something qsTile.state = Tile.STATE_INACTIVE } Tile.STATE_INACTIVE -> { // do something qsTile.state = Tile.STATE_ACTIVE } } qsTile.updateTile() }
注意しなければならないのは、 Tile#setState()
しただけでは表示は切り替わらず、
Tile#updateTile()
をコールしてやる必要があります。
Kotlinで書くとproperty accessなのでちょっと意味がわかりにくいかもですが お察しください。
Quick SettingsにTileが追加できるようになる仕組み
せっかくなので上で登録したサービスがどのような仕組みでQuick Settingsに追加できるようになるのかFrameworkのコードを読んでみました。
まず、Quick Settingsのviewが QSCustomizer
というLinearLayoutを拡張したクラスを持っています。
(何をQuick Settingsに置くか選べる全画面のview部分です)
このviewの表示時にQSCustomizer#show() がコールされますが、この時 TileQueryHelper
クラスがnewされます。
TileQueryHelper
のコンストラクタで呼ばれる addSystemTiles()
の中で AsyncTask
の拡張である QueryTilesTask
をHandlerにpostします。そのタスクのbackground処理の中でマニフェストにintent filter(TileService.ACTION_QS_TILE
) を登録してあるサービス一覧を撮ってきてtileにaddしているという仕組みでした。
140 private class QueryTilesTask extends 141 AsyncTask<Collection<QSTile<?>>, Void, Collection<TileInfo>> { 142 @Override 143 protected Collection<TileInfo> doInBackground(Collection<QSTile<?>>... params) { 144 List<TileInfo> tiles = new ArrayList<>(); 145 PackageManager pm = mContext.getPackageManager(); 146 List<ResolveInfo> services = pm.queryIntentServicesAsUser( 147 new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser()); 148 for (ResolveInfo info : services) { 149 String packageName = info.serviceInfo.packageName; 150 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name); 151 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm); 152 String spec = CustomTile.toSpec(componentName); 153 State state = getState(params[0], spec); 154 if (state != null) { 155 addTile(spec, appLabel, state, false); 156 continue; 157 }
queryIntentServicesAsUser()
、便利そうですが @hideなAPIですね。残念。
それにしてもAsyncTaskとかMessageとHandlerの組み合わせで非同期処理を行うパターン、AndroidのFrameworkでよく見かけます。
個人的にこのパターン結構好きです。