ユーザー補助サービスは、障がいがあるユーザーや一時的にデバイスを十分に操作できないユーザーを支援するために、ユーザー インターフェースを拡張するアプリです。これらのサービスはバックグラウンドで実行され、システムと通信して画面のコンテンツを検査し、ユーザーに代わってアプリを操作します。たとえば、スクリーン リーダー(TalkBack など)、スイッチ アクセス ツール、音声操作システムなどがあります。
このガイドでは、Android ユーザー補助サービスの構築の基本について説明します。
ユーザー補助サービスのライフサイクル
ユーザー補助サービスを作成するには、AccessibilityService クラスを拡張し、アプリのマニフェストでサービスを宣言する必要があります。
サービス クラスを作成する
AccessibilityService を拡張するクラスを作成します。次のメソッドをオーバーライドする必要があります。
onAccessibilityEvent: サービスの設定に一致するイベント(フォーカスの変更やボタンのクリックなど)がシステムで検出されたときに呼び出されます。サービスがユーザー インターフェースを解釈する場所です。onInterrupt: システムがサービスのフィードバックを中断したときに呼び出されます(たとえば、ユーザーがフォーカスをすばやく移動したときに音声出力を停止する場合など)。
package com.example.android.apis.accessibility import android.accessibilityservice.AccessibilityService import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.FingerprintGestureController import android.accessibilityservice.AccessibilityButtonController import android.accessibilityservice.GestureDescription import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.graphics.Path import android.os.Build import android.media.AudioManager import android.content.Context class MyAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { // Interpret the event and provide feedback to the user } override fun onInterrupt() { // Interrupt any ongoing feedback } override fun onServiceConnected() { // Perform initialization here } }
マニフェストで宣言する
AndroidManifest.xml ファイルにサービスを登録します。システムのみがサービスにバインドできるように、BIND_ACCESSIBILITY_SERVICE 権限を厳密に適用する必要があります。
設定ボタンが機能するように、ServiceSettingsActivity を宣言します。
<application> <service android:name=".accessibility.MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="true" android:label="@string/accessibility_service_label"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> <activity android:name=".accessibility.ServiceSettingsActivity" android:exported="true" android:label="@string/accessibility_service_settings_label" /> </application>
サービスを構成する
res/xml/accessibility_service_config.xml に構成ファイルを作成します。このファイルでは、サービスが処理するイベントと提供するフィードバックを定義します。マニフェストで宣言した ServiceSettingsActivity を参照してください。
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagDefault|flagRequestFingerprintGestures|flagRequestAccessibilityButton" android:accessibilityFeedbackType="feedbackSpoken" android:notificationTimeout="100" android:canRetrieveWindowContent="true" android:canPerformGestures="true" android:settingsActivity="com.example.android.apis.accessibility.ServiceSettingsActivity" />
構成ファイルには、次のキー属性が含まれます。
android:accessibilityEventTypes: 受信するイベント。汎用サービスにはtypeAllMaskを使用します。android:canRetrieveWindowContent: サービスが UI 階層を検査する必要がある場合(画面からテキストを読み取る場合など)、trueにする必要があります。android:canPerformGestures: ジェスチャー(スワイプやタップなど)をプログラムでディスパッチする場合は、trueにする必要があります。android:accessibilityFlags: フラグを組み合わせて機能を有効にします。指紋ジェスチャーにはflagRequestFingerprintGesturesが必要です。ソフトウェアのユーザー補助機能ボタンにはflagRequestAccessibilityButtonが必要です。
構成オプションの完全なリストについては、AccessibilityServiceInfo をご覧ください。
実行時構成
XML 構成は静的ですが、実行時にサービス構成を動的に変更することもできます。これは、ユーザー設定に基づいて機能を切り替える場合に便利です。
onServiceConnected() をオーバーライドして、setServiceInfo() を使用してランタイム アップデートを適用します。
override fun onServiceConnected() { val info = AccessibilityServiceInfo() // Set the type of events that this service wants to listen to. info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED // Set the type of feedback your service provides. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN // Set flags at runtime. info.flags = AccessibilityServiceInfo.FLAG_DEFAULT or AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES this.setServiceInfo(info) }
UI コンテンツを解釈する
onAccessibilityEvent() がトリガーされると、システムは AccessibilityEvent を提供します。このイベントは、画面コンテンツの階層表現であるユーザー補助ツリーへのエントリ ポイントとして機能します。
サービスは、主に AccessibilityNodeInfo オブジェクトとやり取りします。このオブジェクトは、ボタン、リスト、テキストなどの UI 要素を表します。これらの UI 要素に関するデータは AccessibilityNodeInfo に正規化されます。
次の例は、イベントのソースを取得し、ユーザー補助ツリーをトラバースして情報を検索する方法を示しています。
override fun onAccessibilityEvent(event: AccessibilityEvent) { // Get the source node of the event val sourceNode: AccessibilityNodeInfo? = event.source if (sourceNode == null) return // Inspect properties if (sourceNode.isCheckable) { val state = if (sourceNode.isChecked) "Checked" else "Unchecked" val label = sourceNode.text ?: sourceNode.contentDescription // Provide feedback (for example, speak to the user) speakToUser("$label is $state") } // Always recycle nodes to prevent memory leaks sourceNode.recycle() } private fun speakToUser(text: String) { // Your text-to-speech implementation goes here }
ユーザーに代わって操作する
ユーザー補助サービスは、ユーザーに代わってボタンのクリックやリストのスクロールなどの操作を行うことができます。
アクションを実行するには、AccessibilityNodeInfo オブジェクトで performAction() を呼び出します。
fun performClick(node: AccessibilityNodeInfo) { if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
システム全体に影響するグローバル アクション(戻るボタンを押す、通知シェードを開くなど)には、performGlobalAction() を使用します。
// Navigate back fun navigateBack() { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) }
フォーカスを管理する
Android には、入力フォーカス(キーボード入力の送信先)とユーザー補助フォーカス(ユーザー補助サービスが検査しているもの)の 2 種類のフォーカスがあります。
次のスニペットは、現在アクセシビリティ フォーカスがある要素を見つける方法を示しています。
// Find the node that currently has accessibility focus // Note: rootInActiveWindow can be null if the window is not available val root = rootInActiveWindow if (root != null) { val focusedNode = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) // Do something with focusedNode // Always recycle nodes focusedNode?.recycle() // rootInActiveWindow doesn't need to be recycled, but obtained nodes do. }
次のスニペットは、ユーザー補助フォーカスを特定の要素に移動する方法を示しています。
// Request that the system give focus to a given node fun focusNode(node: AccessibilityNodeInfo) { node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) }
ユーザー補助サービスを作成する際は、ユーザーのフォーカス状態を尊重し、ユーザー操作によって明示的にトリガーされない限り、フォーカスを奪わないようにします。
操作の実行
サービスは、スワイプ、タップ、マルチタッチ操作などのカスタム ジェスチャーを画面にディスパッチできます。これを行うには、dispatchGesture() API を使用できるように、構成で android:canPerformGestures="true" を宣言します。
シンプルなジェスチャー
簡単なジェスチャーを実行するには、まず、特定のジェスチャーに関連付けられた動きを表す Path オブジェクトを作成します。次に、Path を GestureDescription でラップしてストロークを記述します。最後に、dispatchGesture を呼び出してジェスチャーをディスパッチします。
fun swipeRight() { // Create a path for the swipe (from x=100 to x=500) val swipePath = Path() swipePath.moveTo(100f, 500f) swipePath.lineTo(500f, 500f) // Build the stroke description (0ms delay, 500ms duration) val stroke = GestureDescription.StrokeDescription(swipePath, 0, 500) // Build the gesture description val gestureBuilder = GestureDescription.Builder() gestureBuilder.addStroke(stroke) // Dispatch the gesture dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { super.onCompleted(gestureDescription) // Gesture finished successfully } }, null) }
連続ジェスチャー
複雑な操作(L 字を描く、正確な複数ステップのドラッグなど)の場合、willContinue パラメータを使用してストロークを連結できます。
fun performLShapedGesture() { val path1 = Path().apply { moveTo(200f, 200f) lineTo(400f, 200f) } val path2 = Path().apply { moveTo(400f, 200f) lineTo(400f, 400f) } // First stroke: willContinue = true val stroke1 = GestureDescription.StrokeDescription(path1, 0, 500, true) // Second stroke: continues immediately after stroke1 val stroke2 = stroke1.continueStroke(path2, 0, 500, false) val builder = GestureDescription.Builder() builder.addStroke(stroke1) builder.addStroke(stroke2) dispatchGesture(builder.build(), null, null) }
音声管理
ユーザー補助サービス(特にスクリーン リーダー)を作成する場合は、STREAM_ACCESSIBILITY 音声ストリームを使用します。これにより、ユーザーはシステム メディアの音量とは別にサービスの音量を制御できます。
fun increaseAccessibilityVolume() { val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_ACCESSIBILITY, AudioManager.ADJUST_RAISE, 0 ) }
構成に FLAG_ENABLE_ACCESSIBILITY_VOLUME フラグを必ず含めてください。XML で指定するか、実行時に setServiceInfo を使用して指定します。
高度な機能
指紋認証センサーでの操作
Android 10(API レベル 29)以上を搭載したデバイスでは、サービスで指紋認証センサーでの方向スワイプをキャプチャできます。これは、代替のナビゲーション コントロールを提供する場合に便利です。
onServiceConnected() メソッドに次のロジックを追加します。
// Import: android.os.Build // Import: android.accessibilityservice.FingerprintGestureController private var gestureController: FingerprintGestureController? = null override fun onServiceConnected() { // Check if the device is running Android 10 (Q) or higher if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { gestureController = fingerprintGestureController val callback = object : FingerprintGestureController.FingerprintGestureCallback() { override fun onGestureDetected(gesture: Int) { when (gesture) { FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN -> { // Handle swipe down } FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP -> { // Handle swipe up } } } } gestureController?.registerFingerprintGestureCallback(callback, null) } }
ユーザー補助機能ボタン
ソフトウェア ナビゲーション キーを使用するデバイスでは、ユーザーはナビゲーション バーのユーザー補助機能ボタンからサービスを呼び出すことができます。
この機能を使用するには、サービス構成に FLAG_REQUEST_ACCESSIBILITY_BUTTON フラグを追加します。次に、登録ロジックを onServiceConnected() メソッドに追加します。
// Import: android.accessibilityservice.AccessibilityButtonController override fun onServiceConnected() { // ... existing initialization code ... val controller = accessibilityButtonController controller.registerAccessibilityButtonCallback( object : AccessibilityButtonController.AccessibilityButtonCallback() { override fun onClicked(controller: AccessibilityButtonController) { // Respond to button tap } } ) }
多言語のテキスト読み上げ
テキストを読み上げるサービスは、ソーステキストに LocaleSpan タグが付いている場合、言語を自動的に切り替えることができます。これにより、サービスは手動で切り替えることなく、多言語コンテンツを正しく発音できます。
import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.LocaleSpan import java.util.Locale // Wrap text in LocaleSpan to indicate language val spannable = SpannableStringBuilder("Bonjour") spannable.setSpan( LocaleSpan(Locale.FRANCE), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )
サービスが AccessibilityNodeInfo を処理するときは、LocaleSpan オブジェクトの text プロパティを調べて、正しいテキスト読み上げ言語を特定します。
参考情報
詳細については、次のリソースをご覧ください。