Usługa ułatwień dostępu to aplikacja, która ulepsza interfejs użytkownika, aby pomagać osobom z niepełnosprawnościami lub osobom, które tymczasowo nie mogą w pełni korzystać z urządzenia. Te usługi działają w tle i komunikują się z systemem, aby sprawdzać zawartość ekranu i wchodzić w interakcje z aplikacjami w imieniu użytkownika. Przykładami są czytniki ekranu (np. TalkBack), narzędzia Switch Access i systemy sterowania głosem.
Ten przewodnik zawiera podstawowe informacje o tworzeniu usługi ułatwień dostępu na Androida.
Cykl życia usługi ułatwień dostępu
Aby utworzyć usługę ułatwień dostępu, musisz rozszerzyć klasę
AccessibilityService
i zadeklarować usługę w pliku manifestu aplikacji.
Tworzenie klasy usługi
Utwórz klasę, która rozszerza AccessibilityService. Musisz zastąpić te metody:
onAccessibilityEvent: wywoływana, gdy system wykryje zdarzenie, które pasuje do konfiguracji usługi (np. zmianę fokusu lub kliknięcie przycisku). W tym miejscu usługa interpretuje interfejs.onInterrupt: wywoływana, gdy system przerywa działanie usługi (np. aby zatrzymać odpowiedź głosową, gdy użytkownik szybko przenosi fokus).
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 } }
Deklarowanie w pliku manifestu
Zarejestruj usługę w pliku AndroidManifest.xml. Musisz ściśle egzekwować uprawnienie BIND_ACCESSIBILITY_SERVICE, aby tylko system mógł powiązać się z Twoją usługą.
Aby przycisk ustawień działał, zadeklaruj 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>
Konfigurowanie usługi
Utwórz plik konfiguracji w folderze res/xml/accessibility_service_config.xml. Ten plik określa, jakie zdarzenia obsługuje Twoja usługa i jakie informacje zwrotne przekazuje.
Odwołaj się do ServiceSettingsActivity zadeklarowanego w pliku manifestu:
<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" />
Plik konfiguracji zawiera te kluczowe atrybuty:
android:accessibilityEventTypes: zdarzenia, które chcesz otrzymywać. UżyjtypeAllMaskw przypadku usługi do zwykłych obciążeń.android:canRetrieveWindowContent: musi mieć wartośćtrue, jeśli usługa musi sprawdzać hierarchię interfejsu (np. aby odczytać tekst z ekranu).android:canPerformGestures: musi mieć wartośćtrue, jeśli zamierzasz programowo wysyłać gesty (np. przesuwanie lub klikanie).android:accessibilityFlags: łącz flagi, aby włączyć funkcje. Do korzystania z gestów odciskiem palca wymagany jest programflagRequestFingerprintGestures.flagRequestAccessibilityButtonjest wymagane w przypadku przycisku ułatwień dostępu do oprogramowania.
Pełną listę opcji konfiguracji znajdziesz w AccessibilityServiceInfo.
Konfiguracja środowiska wykonawczego
Konfiguracja XML jest statyczna, ale możesz też dynamicznie modyfikować konfigurację usługi w czasie działania. Jest to przydatne do przełączania funkcji na podstawie preferencji użytkownika.
Zastąp onServiceConnected(), aby zastosować aktualizacje środowiska wykonawczego za pomocą 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) }
Interpretowanie treści interfejsu
Gdy onAccessibilityEvent() zostanie wywołany, system udostępni AccessibilityEvent. To zdarzenie jest punktem wejścia do drzewa ułatwień dostępu, czyli hierarchicznej reprezentacji treści na ekranie.
Twoja usługa wchodzi w interakcję głównie z AccessibilityNodeInfo obiektami, które reprezentują elementy interfejsu, takie jak przyciski, listy i tekst. Dane o tych elementach interfejsu są normalizowane do postaci AccessibilityNodeInfo.
Poniższy przykład pokazuje, jak pobrać źródło zdarzenia i przejść przez drzewo ułatwień dostępu, aby znaleźć informacje.
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 }
Działanie w imieniu użytkowników
Usługi ułatwień dostępu mogą wykonywać działania w imieniu użytkownika, np. klikać przyciski lub przewijać listy.
Aby wykonać działanie, wywołaj funkcję performAction() na obiekcie AccessibilityNodeInfo.
fun performClick(node: AccessibilityNodeInfo) { if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
W przypadku działań globalnych, które mają wpływ na cały system (np. naciśnięcie przycisku Wstecz lub otwarcie obszaru powiadomień), użyj performGlobalAction().
// Navigate back fun navigateBack() { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) }
Zarządzanie skupieniem
Android ma 2 rodzaje fokusu: fokus wejściowy (miejsce, do którego trafia wprowadzany tekst) i fokus ułatwień dostępu (element sprawdzany przez usługę ułatwień dostępu).
Poniższy fragment kodu pokazuje, jak znaleźć element, który ma obecnie fokus ułatwień dostępu:
// 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. }
Poniższy fragment kodu pokazuje, jak przenieść fokus ułatwień dostępu na konkretny element:
// Request that the system give focus to a given node fun focusNode(node: AccessibilityNodeInfo) { node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) }
Podczas tworzenia usługi ułatwień dostępu należy uwzględniać stan fokusu użytkownika i unikać przejmowania fokusu, chyba że zostało to wyraźnie wywołane przez działanie użytkownika.
Obsługa gestów
Usługa może wysyłać na ekran niestandardowe gesty, takie jak przesunięcia, kliknięcia czy interakcje wielodotykowe. Aby to zrobić, zadeklaruj android:canPerformGestures="true" w konfiguracji, aby móc używać interfejsu dispatchGesture() API.
Proste gesty
Aby wykonywać proste gesty, zacznij od utworzenia obiektu Path, który będzie reprezentować ruch związany z danym gestem. Następnie umieść Path w GestureDescription, aby opisać pociągnięcie. Na koniec zadzwoń pod numer dispatchGesture, aby wysłać gest.
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) }
Ciągłe gesty
W przypadku złożonych interakcji (takich jak narysowanie kształtu litery L lub wykonanie precyzyjnego wieloetapowego przeciągnięcia) możesz połączyć ze sobą pociągnięcia za pomocą parametru 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) }
Zarządzanie dźwiękiem
Podczas tworzenia usługi ułatwień dostępu (zwłaszcza czytnika ekranu) używaj STREAM_ACCESSIBILITYstrumienia audio. Dzięki temu użytkownicy mogą sterować głośnością usługi niezależnie od głośności multimediów systemowych.
fun increaseAccessibilityVolume() { val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_ACCESSIBILITY, AudioManager.ADJUST_RAISE, 0 ) }
Pamiętaj, aby w konfiguracji uwzględnić flagę FLAG_ENABLE_ACCESSIBILITY_VOLUME – w pliku XML lub za pomocą setServiceInfo w czasie działania.
Funkcje zaawansowane
Gesty związane z odciskiem palca
Na urządzeniach z Androidem 10 (API na poziomie 29) lub nowszym usługa może rejestrować przesunięcia w określonym kierunku na czytniku linii papilarnych. Przydaje się to do udostępniania alternatywnych elementów sterujących nawigacją.
Dodaj do metody onServiceConnected() tę logikę:
// 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) } }
Przycisk ułatwień dostępu
Na urządzeniach z programowymi klawiszami nawigacyjnymi użytkownicy mogą wywołać Twoją usługę za pomocą przycisku ułatwień dostępu na pasku nawigacyjnym.
Aby korzystać z tej funkcji, dodaj flagę FLAG_REQUEST_ACCESSIBILITY_BUTTON do konfiguracji usługi. Następnie dodaj logikę rejestracji do metody 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 } } ) }
Wielojęzyczna zamiana tekstu na mowę
Usługa odczytująca tekst na głos może automatycznie przełączać języki, jeśli tekst źródłowy jest oznaczony tagiem LocaleSpan. Dzięki temu usługa może poprawnie odczytywać treści w różnych językach bez konieczności ręcznego przełączania.
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 )
Gdy usługa przetwarza AccessibilityNodeInfo, sprawdź właściwość text w przypadku obiektów LocaleSpan, aby określić prawidłowy język syntezy mowy.
Dodatkowe materiały
Więcej informacji znajdziesz w tych materiałach:
Przewodniki
- Tworzenie aplikacji z ułatwieniami dostępu
- Ułatwienia dostępu w Jetpack Compose
- Krótki przewodnik: ułatwienia dostępu w Compose