Activityで必要な設定が全て許可されたら、Serviceが起動されます。Service起動中にユーザーが設定をOFFにする可能性があるので、本来であればServiceを起動してから設定を監視するようにした方がより実用的です。
本稿のサンプルアプリのServiceは「MainService.java」に実装があります。Serviceも処理の流れを追う形で実装を説明していきます。
まずはServiceで実装必須の「onBind(Intent)」を見てみます。
@Nullable @Override public IBinder onBind(Intent intent) { return null; }
このアプリのServiceはバインドしないので、このメソッドはnullを返します。
次はService起動時に呼び出される「onStartCommand(Intent, int, int)」を見てみます。
@Override public int onStartCommand(Intent intent, int flags, int startId) { WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams( 0, // 【1】 0, // 【1】 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, // 【2】 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 【3】 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.OPAQUE); // 【4】 mView = new View(this); // 【5】 mView.setOnTouchListener(this); wm.addView(mView, params); // 【6】 return START_STICKY; // 【7】 }
このメソッドはService起動時に呼び出されるコールバックで、今回のアプリで最も重要なオーバーレイViewの設定を含んでいます。
今回のサンプルアプリはオーバーレイViewを非表示にしたいため、【1】で高さと幅を0にしています。【2】ではtypeにTYPE_SYSTEM_ALERTを指定します。
【3】でflagを指定します。FLAG_NOT_FOCUSABLEはフォーカスを取得してしまわないようにするフラグで、これがないと全てのタッチイベントをこのアプリが横取りしてしまい、端末が操作不可能になってしまいます。
FLAG_WATCH_OUTSIDE_TOUCHは、Viewの外部のタッチイベントを取得するためのフラグです。サイズ0の非表示のオーバーレイViewを常に表示しつつ、フォーカスを受け付けないようにしておくことで、サイズが0であることにより、通知領域とソフトウェアキーを除く画面全域のタッチイベントを全て取得できるわけです。
【4】のformatは何も描画しないので何を指定しても問題ありません。PixelFormat.UNKNOWNでも問題ないことを確認していますが、今回は無難にPixelFormat.OPAQUEとしました。
【5】でViewのインスタンスを生成し、タッチイベントのリスナーを設定します。【6】であらかじめ取得しておいたWindowManagerに生成しておたLayoutParmsと一緒にViewを追加します。
Service#onStartCommand(Intent, int, int)は戻り値でサービスが強制終了された際の振る舞いを決定します。戻り値とその意味は以下を参照してください。
戻り値 | 意味 |
---|---|
START_STICKY_COMPATIBILITY | Serviceが強制終了してもonStartCommand(Intent, int, int)が呼び出されることが保証されない |
START_STICKY | Serviceが強制終了したらonStartCommand(Intent, int, int)呼び出される、ただしIntentにはnullが渡される |
START_NOT_STICKY | Serviceが強制終了してもonStartCommand(Intent, int, int)は呼び出されない |
START_REDELIVER_INTENT | Serviceが強制終了したらonStartCommand(Intent, int, int)がstartService(Intent)呼び出され、前回と同じIntentが渡される |
今回のアプリはIntentを使用しないため、【7】のようにSTART_STICKYを返します。
今回のサンプルアプリは、オーバーレイViewのサイズを0にして非表示にしていますが、このオーバーレイViewを見えるようにした場合、どのように表示されるかというサンプルが以下になります。
オーバーレイViewはホーム画面を含む、あらゆるアプリの最前面に表示されます。画像はないですがカメラビュー上にも表示されます。筆者が表示を確認できなかったのはロック画面だけです。また、電源ボタン長押しで表示されるダイアログが表示されると、表示はされるもののタッチイベントは取得できませんでした。通知を引っ張り出しても同様です。
このダイアログや通知よりも手前にViewを出すには、LayoutParamsのtypeにTYPE_SYSTEM_ALERTではなくTYPE_SYSTEM_OVERLAYを指定します。
TYPE_SYSTEM_OVERLAYならばロック画面にもViewを表示することが可能です。
ただし、TYPE_SYSTEM_OVERLAYでは一切のタッチイベントを取得できません。
操作可能なViewを最上位に表示するには、恐らく「TYPE_STATUS_BAR」「TYPE_SYSTEM_DIALOG」といった、別のtype指定が必要なのだと思います。これらのtypeを使用してViewを描画しようとすると、Android内部ではパーミッションの許可チェックを「"android.permission.INTERNAL_SYSTEM_WINDOW"」という文字列で行います。
ただし、この定義を単にAndroidManifest.xmlに以下のように記述するだけでは許可は与えられないようで、例えば署名を行うキーストアだったり、あるいはパッケージ名だったりというAndroidManifest.xml以外の設定が必要になるのではないかというのが筆者の結論です。
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
Copyright © ITmedia, Inc. All Rights Reserved.