Android Tips #40 AccessibilityService でユーザー補助サービスを作ってみる
AccessibilityService とは
AccessibilityService とは障害者や高齢者などのハンディキャップのあるユーザーでもアプリを使うことができるように補助する機能のことです。
例えば Android に標準搭載されている TalkBack はお知らせ (Notification) やユーザーの操作を音声またはバイブレーションで
フィードバックしてくれます。視覚にハンディキャップがあるユーザーにとって優しいサービスですよね。
今回はそんな AccessibilityService を使ったオリジナルのユーザー補助サービスの実装を通してユーザー補助サービスの作りかたを学びたいと思います!
ユーザー補助サービスを作ってみる
ユーザー補助サービスの概要についてざっくりわかったところで、早速作ってみましょう! 今回はサンプルということで操作内容を Toast で表示する機能を作りたいと思います。
1. AccessibilityService を作る
まずは AccessibilityService を継承した Service クラスを作ります。abstract なメソッドなど最低限な実装をすると以下のような感じになります。
package jp.classmethod.android.sample.accessibilityservice; import android.accessibilityservice.AccessibilityService; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.view.accessibility.AccessibilityEvent; import android.widget.Toast; public class ToastAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { } @Override public void onInterrupt() { } }
AccessibilityService#onAccessibilityEvent() はユーザーが操作したり Notification を受け取ったりといったユーザー補助できるアクションが実行されたときに実行されるメソッドです。音声で知らせたいときはこのタイミングで音を鳴らしたりします。また AccessibilityService#onInterrupt() はユーザー操作などによってこのサービス自体の提供が中断されたときに呼び出されます。処理が特に不要な場合は何も書かなくて良いです。
今回はどんなユーザーアクションが起きたか Toast で表示させたいと思うので AccessibilityService#onAccessibilityEvent() メソッドを以下のように実装してみます。
@Override public void onAccessibilityEvent(AccessibilityEvent event) { int type = event.getEventType(); String typeName = ""; switch (type) { // Notificationの表示に変更があったとき case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: typeName = "TYPE_NOTIFICATION_STATE_CHANGED"; break; // View をタップしたとき case AccessibilityEvent.TYPE_VIEW_CLICKED: typeName = "TYPE_VIEW_CLICKED"; break; // View にフォーカスがあたったとき case AccessibilityEvent.TYPE_VIEW_FOCUSED: typeName = "TYPE_VIEW_FOCUSED"; break; // View をロングタップしたとき case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED: typeName = "TYPE_VIEW_LONG_CLICKED"; break; // View が選択されたとき case AccessibilityEvent.TYPE_VIEW_SELECTED: typeName = "TYPE_VIEW_SELECTED"; break; // View のテキストが変更されたとき case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: typeName = "TYPE_VIEW_TEXT_CHANGED"; break; // 画面の表示に変更があったとき case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: typeName = "TYPE_WINDOW_STATE_CHANGED"; break; // アナウンスがあったとき // case AccessibilityEvent.TYPE_ANNOUNCEMENT: case AccessibilityEventCompat.TYPE_ANNOUNCEMENT: typeName = "TYPE_ANNOUNCEMENT"; break; // ジェスチャーが終わったとき // case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: case AccessibilityEventCompat.TYPE_GESTURE_DETECTION_END: typeName = "TYPE_GESTURE_DETECTION_END"; break; // ジェスチャーが始まったとき // case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: case AccessibilityEventCompat.TYPE_GESTURE_DETECTION_START: typeName = "TYPE_GESTURE_DETECTION_START"; break; // タッチ探索が終わったとき // case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: case AccessibilityEventCompat.TYPE_TOUCH_EXPLORATION_GESTURE_END: typeName = "TYPE_TOUCH_EXPLORATION_GESTURE_END"; break; // タッチ探索が始まったとき // case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: case AccessibilityEventCompat.TYPE_TOUCH_EXPLORATION_GESTURE_START: typeName = "TYPE_TOUCH_EXPLORATION_GESTURE_START"; break; // タッチ操作が終わったとき // case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: case AccessibilityEventCompat.TYPE_TOUCH_INTERACTION_END: typeName = "TYPE_TOUCH_INTERACTION_END"; break; // タッチ操作が始まったとき // case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: case AccessibilityEventCompat.TYPE_TOUCH_INTERACTION_START: typeName = "TYPE_TOUCH_INTERACTION_START"; break; // View のアクセシビリティ・フォーカスがクリアされたとき // case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: case AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: typeName = "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED"; break; // View がアクセシビリティ・フォーカスされたとき // case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: case AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED: typeName = "TYPE_VIEW_ACCESSIBILITY_FOCUSED"; break; // View のホバーが始まったとき // case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: case AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER: typeName = "TYPE_VIEW_HOVER_ENTER"; break; // View のホバーが終わったとき // case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: case AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT: typeName = "TYPE_VIEW_HOVER_EXIT"; break; // View をスクロールしたとき // case AccessibilityEvent.TYPE_VIEW_SCROLLED: case AccessibilityEventCompat.TYPE_VIEW_SCROLLED: typeName = "TYPE_VIEW_SCROLLED"; break; // View のテキスト範囲が変更されたとき // case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: case AccessibilityEventCompat.TYPE_VIEW_TEXT_SELECTION_CHANGED: typeName = "TYPE_VIEW_TEXT_SELECTION_CHANGED"; break; // View のテキストを横断したとき // case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: case AccessibilityEventCompat.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: typeName = "TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY"; break; // 画面内のコンテンツが変更されたとき // case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: case AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED: typeName = "TYPE_WINDOW_CONTENT_CHANGED"; break; default: typeName = "UNKNOWN_TYPE"; } if (mToast == null) { mToast = Toast.makeText(getApplicationContext(), typeName, Toast.LENGTH_SHORT); } else { mToast.setText(typeName); } mToast.show(); }
ただひたすらにイベントタイプを取得して Toast に流しています。定数を参照しているところが AccessibilityEvent だったり AccessibilityEventCompat だったりしていますが AccessibilityEventCompat を使っている定数は Android 4.0 (APIレベル14) 以降に追加されたイベントタイプです。Android 4.0 で AccessibilityService の機能が大幅に追加されて、より多くのイベントがハンドリングできるようになりました。ですが Android 4.0 より前のバージョンでもアクセスできるよう Support Package の AccessibilityEventCompat の定数を使っています。これで Android 4.0 以降でもそれ以前でも動作する実装が書けます。
2. AccessibilityService の詳細情報を設定する
(超簡単な) AccessibilityService ができたので次に詳細情報を xml で設定します。 res/xml フォルダ内に xml ファイルを作り、以下のように accessibility-service タグに設定を記述していきます。この xml ファイルは後述する AndroidManifest で設定して読みこむようにします。
<?xml version="1.0" encoding="UTF-8" ?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeAllMask" android:accessibilityFeedbackType="feedbackAllMask" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" android:description="@string/description" />
3. AndroidManifest を定義する
最後に、サービスを AndroidManifest.xml で定義します。普通の Service と同じように application タグ内に service タグで定義します。intent-filter に android.accessibilityservice.AccessibilityService のアクションを入れておくと、アプリがユーザー補助サービスとして登録されます。また meta-data タグを作り、先ほど作成した xml ファイルを詳細情報として設定します。
<service android:name=".ToastAccessibilityService" android:label="AccessibilityServiceSample" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibillity_service_config" /> </service>
2013/12/18 Android 4.1 以降では android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" が必須でしたので追加しました。ご指摘いただいたむとう様、ありがとうございました!
また Android 4.1 以降は android.permission.BIND_ACCESSIBILITY_SERVICE が必要なので入れておきましょう。
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
4. 実行してみる
すごーーーく簡単なサービスができました。実際に使ってみましょう!まずは起動させます。もちろん Service だけなので何も起きません。。 ということで「設定>ユーザー補助」を開きます。
すると、ユーザー補助サービスの一覧が表示されます。その中にいま作ったアプリ名 (SampleAccessibilityService) があると思うので、タップします。
先ほど設定した説明が表示されています。トグルボタンを有効にするとサービスが実行されます!
ホーム画面はもちろん。どのようなアプリでも、画面をタップしたり表示を切り替えたりといったアクションが発生すると Toast を表示してくれます!下図はホームをスクロールしたときのキャプチャです。 Toast がやたらチラチラ表示されて邪魔ですが。。サンプルということでw
ソースコード
簡単なサンプルではありますが、今回実装したソースコードを github に公開しました。ぜひご覧ください♪
suwa-yuki/SampleAccessibilityService
まとめ
AccessibilityService で多くの操作をハンドリングできるので、そこからアイデア次第でさまざまなユーザー支援ができると思います。また "ユーザー補助機能" という名目ではありますが、この機能を使って面白いサービスも実現できそうですね(Notificationの通知をPCにお知らせするアプリなど)。ぜひアイデアを広げて面白い(または便利な)アプリを作ってみてください。