Usługa ułatwień dostępu to aplikacja rozszerzająca interfejs, aby pomagać użytkownikom z niepełnosprawnościami lub tym, którzy tymczasowo nie mogą w pełni korzystać z urządzenia. Na przykład użytkownicy, którzy prowadzą samochód, opiekują się małymi dziećmi lub uczestniczą w bardzo głośnych imprezach, mogą potrzebować dodatkowych informacji o interfejsie.
Android zapewnia standardowe usługi ułatwień dostępu, w tym TalkBack, a deweloperzy mogą tworzyć i rozpowszechniać własne usługi. W tym dokumencie wyjaśniamy podstawy budowania usługi ułatwień dostępu.
Usługę ułatwień dostępu można połączyć w pakiet ze standardową aplikacją lub utworzyć jako samodzielny projekt na Androida. W każdej sytuacji proces tworzenia usługi jest taki sam.
Utwórz swoją usługę ułatwień dostępu
W projekcie utwórz klasę, która rozszerza zakres AccessibilityService
:
Kotlin
package com.example.android.apis.accessibility import android.accessibilityservice.AccessibilityService import android.view.accessibility.AccessibilityEvent class MyAccessibilityService : AccessibilityService() { ... override fun onInterrupt() {} override fun onAccessibilityEvent(event: AccessibilityEvent?) {} ... }
Java
package com.example.android.apis.accessibility; import android.accessibilityservice.AccessibilityService; import android.view.accessibility.AccessibilityEvent; public class MyAccessibilityService extends AccessibilityService { ... @Override public void onAccessibilityEvent(AccessibilityEvent event) { } @Override public void onInterrupt() { } ... }
Jeśli utworzysz nowy projekt dla tego zasobu (Service
) i nie planujesz powiązać z nim żadnej aplikacji, możesz usunąć ze źródła początkową klasę Activity
.
Deklaracje i uprawnienia w pliku manifestu
Aplikacje udostępniające usługi ułatwień dostępu muszą zawierać w pliku manifestu określone deklaracje, aby system Android mógł być traktowany jako usługa ułatwień dostępu. W tej sekcji opisano wymagane i opcjonalne ustawienia usług ułatwień dostępu.
Deklaracja dotycząca usługi ułatwień dostępu
Aby Twoja aplikacja była traktowana jako usługa ułatwień dostępu, umieść w elemencie application
element service
zamiast elementu activity
w pliku manifestu. Dodatkowo w elemencie service
umieść filtr intencji usługi ułatwień dostępu. Plik manifestu musi też chronić usługę przez dodanie uprawnienia BIND_ACCESSIBILITY_SERVICE
, aby możliwe było powiązanie z nim tylko systemu. Oto przykład:
<application> <service android:name=".MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:label="@string/accessibility_service_label"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> </service> </application>
Konfiguracja usługi ułatwień dostępu
Usługi ułatwień dostępu muszą zawierać konfigurację określającą typy zdarzeń ułatwień dostępu obsługiwane przez usługę oraz dodatkowe informacje na jej temat. Konfiguracja usługi ułatwień dostępu znajduje się w klasie AccessibilityServiceInfo
. Twoja usługa może utworzyć i ustawić konfigurację za pomocą instancji tej klasy i klasy setServiceInfo()
w środowisku wykonawczym. Jednak nie wszystkie opcje konfiguracji są dostępne przy użyciu tej metody.
W pliku manifestu możesz uwzględnić element <meta-data>
zawierający odwołanie do pliku konfiguracji, co pozwoli ustawić pełny zakres opcji dla Twojej usługi ułatwień dostępu, jak pokazano w tym przykładzie:
<service android:name=".MyAccessibilityService"> ... <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>
Ten element <meta-data>
odnosi się do pliku XML, który tworzysz w katalogu zasobów aplikacji: <project_dir>/res/xml/accessibility_service_config.xml>
. Ten kod przedstawia przykładową zawartość pliku konfiguracji usługi:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:packageNames="com.example.android.apis" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagDefault" android:accessibilityFeedbackType="feedbackSpoken" android:notificationTimeout="100" android:canRetrieveWindowContent="true" android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity" />
Więcej informacji o atrybutach XML, których można używać w pliku konfiguracji usługi ułatwień dostępu, znajdziesz w tej dokumentacji:
android:description
android:packageNames
android:accessibilityEventTypes
android:accessibilityFlags
android:accessibilityFeedbackType
android:notificationTimeout
android:canRetrieveWindowContent
android:settingsActivity
Więcej informacji o tym, które ustawienia konfiguracji można dynamicznie ustawiać w czasie działania, znajdziesz w dokumentacji referencyjnej AccessibilityServiceInfo
.
Skonfiguruj usługę ułatwień dostępu
Podczas ustawiania zmiennych konfiguracji usługi ułatwień dostępu weź pod uwagę te kwestie, aby poinformować system o sposobie i czasie uruchamiania:
- Na jakie typy zdarzeń ma odpowiadać?
- Czy usługa musi być aktywna dla wszystkich aplikacji, czy tylko dla określonych nazw pakietów?
- Jakiego typu opinii używa?
Te zmienne możesz konfigurować na 2 sposoby. Jeśli chodzi o zgodność wsteczną, możesz ustawić je w kodzie za pomocą metody setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)
. Aby to zrobić, zastąp metodę onServiceConnected()
i skonfiguruj w niej usługę, jak w tym przykładzie:
Kotlin
override fun onServiceConnected() { info.apply { // Set the type of events that this service wants to listen to. Others // aren't passed to this service. eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED // If you only want this service to work with specific apps, set their // package names here. Otherwise, when the service is activated, it // listens to events from all apps. packageNames = arrayOf("com.example.android.myFirstApp", "com.example.android.mySecondApp") // Set the type of feedback your service provides. feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN // Default services are invoked only if no package-specific services are // present for the type of AccessibilityEvent generated. This service is // app-specific, so the flag isn't necessary. For a general-purpose // service, consider setting the DEFAULT flag. // flags = AccessibilityServiceInfo.DEFAULT; notificationTimeout = 100 } this.serviceInfo = info }
Java
@Override public void onServiceConnected() { // Set the type of events that this service wants to listen to. Others // aren't passed to this service. info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED; // If you only want this service to work with specific apps, set their // package names here. Otherwise, when the service is activated, it listens // to events from all apps. info.packageNames = new String[] {"com.example.android.myFirstApp", "com.example.android.mySecondApp"}; // Set the type of feedback your service provides. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN; // Default services are invoked only if no package-specific services are // present for the type of AccessibilityEvent generated. This service is // app-specific, so the flag isn't necessary. For a general-purpose service, // consider setting the DEFAULT flag. // info.flags = AccessibilityServiceInfo.DEFAULT; info.notificationTimeout = 100; this.setServiceInfo(info); }
Drugą opcją jest skonfigurowanie usługi za pomocą pliku XML. Niektóre opcje konfiguracji, takie jak canRetrieveWindowContent
, są dostępne tylko wtedy, gdy skonfigurujesz usługę przy użyciu kodu XML. Opcje konfiguracji z poprzedniego przykładu wyglądają tak po zdefiniowaniu przy użyciu pliku XML:
<accessibility-service android:accessibilityEventTypes="typeViewClicked|typeViewFocused" android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp" android:accessibilityFeedbackType="feedbackSpoken" android:notificationTimeout="100" android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity" android:canRetrieveWindowContent="true" />
Jeśli używasz pliku XML, odwołaj się do niego w pliku manifestu, dodając do deklaracji usługi tag <meta-data>
wskazujący plik XML. Jeśli przechowujesz plik XML w lokalizacji res/xml/serviceconfig.xml
, nowy tag wygląda tak:
<service android:name=".MyAccessibilityService"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/serviceconfig" /> </service>
Metody usługi ułatwień dostępu
Usługa ułatwień dostępu musi stanowić rozszerzenie klasy AccessibilityService
i zastąpić poniższe metody z tej klasy. Metody są prezentowane w kolejności ich wywoływania przez system Android: od momentu uruchomienia usługi (onServiceConnected()
), przez czas działania (onAccessibilityEvent()
, onInterrupt()
) aż do momentu wyłączenia (onUnbind()
).
onServiceConnected()
: (opcjonalnie) system wywołuje tę metodę po nawiązaniu połączenia z usługą ułatwień dostępu. Korzystając z tej metody, możesz przeprowadzić jednorazową konfigurację usługi, w tym połączyć się z usługami systemu przesyłania opinii użytkowników, takimi jak menedżer dźwięku czy urządzenie wibrujące. Jeśli chcesz skonfigurować usługę w czasie działania lub wprowadzić zmiany jednorazowe, jest to wygodne miejsce do wywołania funkcjisetServiceInfo()
.onAccessibilityEvent()
: (wymagane) system wywołuje tę metodę, gdy wykryjeAccessibilityEvent
pasujący do parametrów filtrowania zdarzeń określonych przez usługę ułatwień dostępu, na przykład gdy użytkownik kliknie przycisk lub zaznaczy element interfejsu w aplikacji, na temat której usługa ułatwień dostępu przekazuje opinię. Gdy system wywołuje tę metodę, przekazuje powiązany elementAccessibilityEvent
, który usługa może następnie zinterpretować i wykorzystać do przekazania użytkownikowi opinii. Metodę tę można wywoływać wiele razy w trakcie cyklu życia usługi.onInterrupt()
: (wymagane) system wywołuje tę metodę, gdy system chce przerwać przesyłanie opinii przez usługę, zwykle w odpowiedzi na działanie użytkownika, takie jak przeniesienie zaznaczenia na inny element sterujący. Tę metodę można wywoływać wiele razy w cyklu życia usługi.onUnbind()
: (opcjonalnie) system wywołuje tę metodę, gdy system ma wyłączyć usługę ułatwień dostępu. Użyj tej metody do jednorazowych procedur wyłączania, w tym anulowania przydziału usług systemu przekazywania opinii użytkowników, takich jak menedżer dźwięku czy wibracja urządzenia.
Te metody wywołania zwrotnego zapewniają podstawową strukturę usługi ułatwień dostępu. Możesz decydować, jak przetwarzać dane dostarczone przez system Android w formie obiektów AccessibilityEvent
i przekazywać użytkownikowi opinię. Aby dowiedzieć się więcej o uzyskiwaniu informacji o zdarzeniu ułatwień dostępu, przeczytaj artykuł Pobieranie szczegółów wydarzenia.
Zarejestruj na potrzeby zdarzeń ułatwień dostępu
Jedną z najważniejszych funkcji parametrów konfiguracji usługi ułatwień dostępu jest możliwość określenia typów zdarzeń ułatwień dostępu, które usługa może obsługiwać. Jeśli podasz te informacje, usługi ułatwień dostępu będą mogły ze sobą współpracować i zapewnią Ci elastyczność obsługi tylko określonych typów zdarzeń z określonych aplikacji. Filtrowanie zdarzeń może obejmować następujące kryteria:
Nazwy pakietów: określ nazwy pakietów aplikacji, których zdarzenia ułatwień dostępu mają obsługiwać Twoją usługę. Jeśli pominiesz ten parametr, usługa ułatwień dostępu będzie uznawana za dostępną dla zdarzeń ułatwień dostępu w dowolnej aplikacji. Możesz ustawić ten parametr w plikach konfiguracji usługi ułatwień dostępu za pomocą atrybutu
android:packageNames
w postaci listy oddzielonej przecinkami lub użyć użytkownikaAccessibilityServiceInfo.packageNames
.Typy zdarzeń: określ typy zdarzeń ułatwień dostępu, które ma obsługiwać Twoja usługa. Możesz ustawić ten parametr w plikach konfiguracji usługi ułatwień dostępu, używając atrybutu
android:accessibilityEventTypes
w postaci listy oddzielonej znakiem|
, na przykładaccessibilityEventTypes="typeViewClicked|typeViewFocused"
. Możesz też określić go za pomocą elementuAccessibilityServiceInfo.eventTypes
.
Podczas konfigurowania usługi ułatwień dostępu zastanów się, jakie zdarzenia może ona obsługiwać, i rejestruj tylko te zdarzenia. Użytkownicy mogą aktywować więcej niż jedną usługę ułatwień dostępu jednocześnie, więc usługa nie może przetwarzać zdarzeń, których nie jest w stanie obsłużyć. Pamiętaj, że inne usługi mogą obsługiwać te zdarzenia, aby poprawić wygodę użytkowników.
Głośność przy ułatwieniach dostępu
Urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym mają kategorię głośności STREAM_ACCESSIBILITY
, która pozwala sterować głośnością wyjścia audio usługi ułatwień dostępu niezależnie od innych dźwięków urządzenia.
Usługi ułatwień dostępu mogą korzystać z tego typu strumienia, ustawiając opcję FLAG_ENABLE_ACCESSIBILITY_VOLUME
. Następnie możesz zmienić głośność dźwięku ułatwień dostępu na urządzeniu, wywołując metodę adjustStreamVolume()
w instancji AudioManager
.
Ten fragment kodu pokazuje, jak usługa ułatwień dostępu może korzystać z kategorii głośności STREAM_ACCESSIBILITY
:
Kotlin
import android.media.AudioManager.* class MyAccessibilityService : AccessibilityService() { private val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager override fun onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) { if (accessibilityEvent.source.text == "Increase volume") { audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY, ADJUST_RAISE, 0) } } }
Java
import static android.media.AudioManager.*; public class MyAccessibilityService extends AccessibilityService { private AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); @Override public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) { AccessibilityNodeInfo interactedNodeInfo = accessibilityEvent.getSource(); if (interactedNodeInfo.getText().equals("Increase volume")) { audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY, ADJUST_RAISE, 0); } } }
Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 6:35.
Skrót ułatwień dostępu
Na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym użytkownicy mogą włączać i wyłączać preferowaną usługę ułatwień dostępu na dowolnym ekranie, naciskając i przytrzymując jednocześnie oba przyciski głośności. Mimo że ten skrót domyślnie włącza i wyłącza TalkBack, użytkownicy mogą skonfigurować ten przycisk, aby włączać i wyłączać dowolną usługę zainstalowaną na ich urządzeniu.
Aby użytkownicy mogli uzyskać dostęp do konkretnej usługi ułatwień dostępu za pomocą skrótu ułatwień dostępu, usługa musi zażądać tej funkcji w czasie działania.
Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 13:25.
Przycisk ułatwień dostępu
Na urządzeniach korzystających z wyrenderowanego programowo obszaru nawigacji i z Androidem 8.0 (poziom interfejsu API 26) lub nowszym po prawej stronie paska nawigacyjnego znajduje się przycisk ułatwień dostępu. Po naciśnięciu tego przycisku użytkownik może wywołać jedną z kilku włączonych funkcji i usług ułatwień dostępu, w zależności od treści wyświetlanej aktualnie na ekranie.
Aby umożliwić użytkownikom wywoływanie danej usługi ułatwień dostępu za pomocą przycisku ułatwień dostępu, usługa musi dodać flagę FLAG_REQUEST_ACCESSIBILITY_BUTTON
do atrybutu android:accessibilityFlags
obiektu AccessibilityServiceInfo
. Usługa może wtedy rejestrować wywołania zwrotne za pomocą registerAccessibilityButtonCallback()
.
Ten fragment kodu pokazuje, jak skonfigurować usługę ułatwień dostępu, aby reagować na naciśnięcie przycisku ułatwień dostępu przez użytkownika:
Kotlin
private var mAccessibilityButtonController: AccessibilityButtonController? = null private var accessibilityButtonCallback: AccessibilityButtonController.AccessibilityButtonCallback? = null private var mIsAccessibilityButtonAvailable: Boolean = false override fun onServiceConnected() { mAccessibilityButtonController = accessibilityButtonController mIsAccessibilityButtonAvailable = mAccessibilityButtonController?.isAccessibilityButtonAvailable ?: false if (!mIsAccessibilityButtonAvailable) return serviceInfo = serviceInfo.apply { flags = flags or AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON } accessibilityButtonCallback = object : AccessibilityButtonController.AccessibilityButtonCallback() { override fun onClicked(controller: AccessibilityButtonController) { Log.d("MY_APP_TAG", "Accessibility button pressed!") // Add custom logic for a service to react to the // accessibility button being pressed. } override fun onAvailabilityChanged( controller: AccessibilityButtonController, available: Boolean ) { if (controller == mAccessibilityButtonController) { mIsAccessibilityButtonAvailable = available } } } accessibilityButtonCallback?.also { mAccessibilityButtonController?.registerAccessibilityButtonCallback(it, null) } }
Java
private AccessibilityButtonController accessibilityButtonController; private AccessibilityButtonController .AccessibilityButtonCallback accessibilityButtonCallback; private boolean mIsAccessibilityButtonAvailable; @Override protected void onServiceConnected() { accessibilityButtonController = getAccessibilityButtonController(); mIsAccessibilityButtonAvailable = accessibilityButtonController.isAccessibilityButtonAvailable(); if (!mIsAccessibilityButtonAvailable) { return; } AccessibilityServiceInfo serviceInfo = getServiceInfo(); serviceInfo.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; setServiceInfo(serviceInfo); accessibilityButtonCallback = new AccessibilityButtonController.AccessibilityButtonCallback() { @Override public void onClicked(AccessibilityButtonController controller) { Log.d("MY_APP_TAG", "Accessibility button pressed!"); // Add custom logic for a service to react to the // accessibility button being pressed. } @Override public void onAvailabilityChanged( AccessibilityButtonController controller, boolean available) { if (controller.equals(accessibilityButtonController)) { mIsAccessibilityButtonAvailable = available; } } }; if (accessibilityButtonCallback != null) { accessibilityButtonController.registerAccessibilityButtonCallback( accessibilityButtonCallback, null); } }
Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 16:28.
Gesty związane z odciskiem palca
Usługi ułatwień dostępu na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym mogą reagować na przesunięcia kierunkowe (w górę, w dół, w lewo i w prawo) wzdłuż czujnika linii papilarnych urządzenia. Aby skonfigurować usługę do otrzymywania wywołań zwrotnych dotyczących tych interakcji, wykonaj te czynności:
- Zadeklaruj uprawnienia
USE_BIOMETRIC
i możliwośćCAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES
. - Ustaw flagę
FLAG_REQUEST_FINGERPRINT_GESTURES
w atrybucieandroid:accessibilityFlags
. - Zarejestruj się na wywołania zwrotne za pomocą
registerFingerprintGestureCallback()
.
Pamiętaj, że nie wszystkie urządzenia mają czujniki linii papilarnych. Aby określić, czy urządzenie obsługuje czujnik, użyj metody isHardwareDetected()
. Nawet na urządzeniu wyposażonym w czytnik linii papilarnych usługa nie może z niego korzystać, gdy jest używany do uwierzytelniania. Aby określić, kiedy czujnik jest dostępny, wywołaj metodę isGestureDetectionAvailable()
i zaimplementuj wywołanie zwrotne onGestureDetectionAvailabilityChanged()
.
Ten fragment kodu pokazuje przykład użycia gestów odcisku palca do poruszania się po wirtualnej planszy:
// AndroidManifest.xml <manifest ... > <uses-permission android:name="android.permission.USE_FINGERPRINT" /> ... <application> <service android:name="com.example.MyFingerprintGestureService" ... > <meta-data android:name="android.accessibilityservice" android:resource="@xml/myfingerprintgestureservice" /> </service> </application> </manifest>
// myfingerprintgestureservice.xml <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" ... android:accessibilityFlags=" ... |flagRequestFingerprintGestures" android:canRequestFingerprintGestures="true" ... />
Kotlin
// MyFingerprintGestureService.kt import android.accessibilityservice.FingerprintGestureController.* class MyFingerprintGestureService : AccessibilityService() { private var gestureController: FingerprintGestureController? = null private var fingerprintGestureCallback: FingerprintGestureController.FingerprintGestureCallback? = null private var mIsGestureDetectionAvailable: Boolean = false override fun onCreate() { gestureController = fingerprintGestureController mIsGestureDetectionAvailable = gestureController?.isGestureDetectionAvailable ?: false } override fun onServiceConnected() { if (mFingerprintGestureCallback != null || !mIsGestureDetectionAvailable) return fingerprintGestureCallback = object : FingerprintGestureController.FingerprintGestureCallback() { override fun onGestureDetected(gesture: Int) { when (gesture) { FINGERPRINT_GESTURE_SWIPE_DOWN -> moveGameCursorDown() FINGERPRINT_GESTURE_SWIPE_LEFT -> moveGameCursorLeft() FINGERPRINT_GESTURE_SWIPE_RIGHT -> moveGameCursorRight() FINGERPRINT_GESTURE_SWIPE_UP -> moveGameCursorUp() else -> Log.e(MY_APP_TAG, "Error: Unknown gesture type detected!") } } override fun onGestureDetectionAvailabilityChanged(available: Boolean) { mIsGestureDetectionAvailable = available } } fingerprintGestureCallback?.also { gestureController?.registerFingerprintGestureCallback(it, null) } } }
Java
// MyFingerprintGestureService.java import static android.accessibilityservice.FingerprintGestureController.*; public class MyFingerprintGestureService extends AccessibilityService { private FingerprintGestureController gestureController; private FingerprintGestureController .FingerprintGestureCallback fingerprintGestureCallback; private boolean mIsGestureDetectionAvailable; @Override public void onCreate() { gestureController = getFingerprintGestureController(); mIsGestureDetectionAvailable = gestureController.isGestureDetectionAvailable(); } @Override protected void onServiceConnected() { if (fingerprintGestureCallback != null || !mIsGestureDetectionAvailable) { return; } fingerprintGestureCallback = new FingerprintGestureController.FingerprintGestureCallback() { @Override public void onGestureDetected(int gesture) { switch (gesture) { case FINGERPRINT_GESTURE_SWIPE_DOWN: moveGameCursorDown(); break; case FINGERPRINT_GESTURE_SWIPE_LEFT: moveGameCursorLeft(); break; case FINGERPRINT_GESTURE_SWIPE_RIGHT: moveGameCursorRight(); break; case FINGERPRINT_GESTURE_SWIPE_UP: moveGameCursorUp(); break; default: Log.e(MY_APP_TAG, "Error: Unknown gesture type detected!"); break; } } @Override public void onGestureDetectionAvailabilityChanged(boolean available) { mIsGestureDetectionAvailable = available; } }; if (fingerprintGestureCallback != null) { gestureController.registerFingerprintGestureCallback( fingerprintGestureCallback, null); } } }
Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 9:03.
Zamiana tekstu na mowę w wielu językach
Począwszy od Androida w wersji 8.0 (poziom interfejsu API 26), usługa zamiany tekstu na mowę na Androida (TTS) może rozpoznawać i odczytywać wyrażenia w wielu językach w jednym bloku tekstu. Aby włączyć tę funkcję automatycznego przełączania języka w usłudze ułatwień dostępu, umieść wszystkie ciągi znaków w obiektach LocaleSpan
w następujący sposób:
Kotlin
val localeWrappedTextView = findViewById<TextView>(R.id.my_french_greeting_text).apply { text = wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE) } private fun wrapTextInLocaleSpan(originalText: CharSequence, loc: Locale): SpannableStringBuilder { return SpannableStringBuilder(originalText).apply { setSpan(LocaleSpan(loc), 0, originalText.length - 1, 0) } }
Java
TextView localeWrappedTextView = findViewById(R.id.my_french_greeting_text); localeWrappedTextView.setText(wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE)); private SpannableStringBuilder wrapTextInLocaleSpan( CharSequence originalText, Locale loc) { SpannableStringBuilder myLocaleBuilder = new SpannableStringBuilder(originalText); myLocaleBuilder.setSpan(new LocaleSpan(loc), 0, originalText.length() - 1, 0); return myLocaleBuilder; }
Więcej informacji znajdziesz w filmie z sesji Google I/O 2017 Co nowego w ułatwieniach dostępu w Androidzie, zaczynając od 10:59.
Działanie w imieniu użytkowników
Od 2011 roku usługi ułatwień dostępu mogą działać w imieniu użytkowników, w tym zmieniać fokus wprowadzania danych i wybierać (aktywować) elementy interfejsu użytkownika. W 2012 r. rozszerzyliśmy zakres działań o listy przewijane i wchodzenie w interakcje z polami tekstowymi. Usługi ułatwień dostępu mogą też wykonywać działania globalne, takie jak przejście do ekranu głównego, naciśnięcie przycisku Wstecz, otwarcie ekranu powiadomień i listy ostatnich aplikacji. Od 2012 roku ułatwiamy korzystanie z ułatwień dostępu, aby usługa ułatwień dostępu mogła zaznaczyć wszystkie widoczne elementy.
Te funkcje pozwalają programistom usług ułatwień dostępu tworzyć alternatywne tryby nawigacji (np. nawigowanie przy użyciu gestów) i umożliwiają użytkownikom z niepełnosprawnościami sprawniejszą kontrolę nad urządzeniami z Androidem.
Nasłuchuj gestów
Usługi ułatwień dostępu są w stanie nasłuchiwać określonych gestów i reagować w imieniu użytkownika. Ta funkcja wymaga, aby usługa ułatwień dostępu
poprosić o aktywację funkcji Czytania dotykiem. Twoja usługa może poprosić o tę aktywację, ustawiając element flags
instancji AccessibilityServiceInfo
usługi na FLAG_REQUEST_TOUCH_EXPLORATION_MODE
, jak pokazano w poniższym przykładzie.
Kotlin
class MyAccessibilityService : AccessibilityService() { override fun onCreate() { serviceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE } ... }
Java
public class MyAccessibilityService extends AccessibilityService { @Override public void onCreate() { getServiceInfo().flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE; } ... }
Gdy Twoja usługa poprosi o aktywację Czytania dotykiem, użytkownik musi zezwolić na włączenie tej funkcji, jeśli nie jest jeszcze aktywna. Gdy ta funkcja jest aktywna, usługa otrzymuje powiadomienia o gestach ułatwień dostępu za pomocą metody wywołania zwrotnego onGesture()
Twojej usługi i może odpowiedzieć w imieniu użytkownika.
Ciągłe gesty
Urządzenia z Androidem 8.0 (poziom interfejsu API 26) obsługują ciągłe gesty lub
gesty automatyczne zawierające więcej niż 1 obiekt Path
.
Określając sekwencję ruchów, możesz wskazać, że są to gesty automatyczne, używając ostatniego argumentu willContinue
w konstruktorze GestureDescription.StrokeDescription
, jak widać w tym fragmencie kodu:
Kotlin
// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down. private fun doRightThenDownDrag() { val dragRightPath = Path().apply { moveTo(200f, 200f) lineTo(400f, 200f) } val dragRightDuration = 500L // 0.5 second // The starting point of the second path must match // the ending point of the first path. val dragDownPath = Path().apply { moveTo(400f, 200f) lineTo(400f, 400f) } val dragDownDuration = 500L val rightThenDownDrag = GestureDescription.StrokeDescription( dragRightPath, 0L, dragRightDuration, true ).apply { continueStroke(dragDownPath, dragRightDuration, dragDownDuration, false) } }
Java
// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down. private void doRightThenDownDrag() { Path dragRightPath = new Path(); dragRightPath.moveTo(200, 200); dragRightPath.lineTo(400, 200); long dragRightDuration = 500L; // 0.5 second // The starting point of the second path must match // the ending point of the first path. Path dragDownPath = new Path(); dragDownPath.moveTo(400, 200); dragDownPath.lineTo(400, 400); long dragDownDuration = 500L; GestureDescription.StrokeDescription rightThenDownDrag = new GestureDescription.StrokeDescription(dragRightPath, 0L, dragRightDuration, true); rightThenDownDrag.continueStroke(dragDownPath, dragRightDuration, dragDownDuration, false); }
Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 15:47.
Korzystanie z ułatwień dostępu
Usługi ułatwień dostępu mogą działać w imieniu użytkowników, aby uprościć interakcję z aplikacjami i zwiększyć wydajność. Możliwość wykonywania działań za pomocą usług ułatwień dostępu została dodana w 2011 roku, a w 2012 r. znacznie się rozszerzyła.
Aby działać w imieniu użytkowników, usługa ułatwień dostępu musi się zarejestrować na otrzymywanie zdarzeń z aplikacji i poprosić o pozwolenie na wyświetlanie zawartości aplikacji, ustawiając android:canRetrieveWindowContent
na true
w pliku konfiguracji usługi. Po odebraniu zdarzeń przez usługę może pobrać ze zdarzenia obiekt AccessibilityNodeInfo
za pomocą getSource()
.
Dzięki obiektowi AccessibilityNodeInfo
usługa może badać hierarchię widoków, aby określić, jakie działanie należy podjąć, a następnie wykonać działania w imieniu użytkownika za pomocą funkcji performAction()
.
Kotlin
class MyAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { // Get the source node of the event. event.source?.apply { // Use the event and node information to determine what action to // take. // Act on behalf of the user. performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) // Recycle the nodeInfo object. recycle() } } ... }
Java
public class MyAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { // Get the source node of the event. AccessibilityNodeInfo nodeInfo = event.getSource(); // Use the event and node information to determine what action to take. // Act on behalf of the user. nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); // Recycle the nodeInfo object. nodeInfo.recycle(); } ... }
Metoda performAction()
umożliwia usłudze wykonanie działania w aplikacji. Jeśli Twoja usługa musi wykonać działanie globalne, takie jak przejście do ekranu głównego, kliknięcie przycisku Wstecz, otwarcie ekranu powiadomień lub listy ostatnich aplikacji, użyj metody performGlobalAction()
.
Użyj typów skupienia
W 2012 roku w interfejsie Androida pojawiła się nowa funkcja: accessibility. Usługi ułatwień dostępu mogą za jego pomocą wybrać dowolny widoczny element interfejsu użytkownika i wykonać związane z nim działania. Ten typ zaznaczenia różni się od fokusu wprowadzania, który określa, który element interfejsu na ekranie ma otrzymać dane wejściowe, gdy użytkownik wpisuje znaki, naciska Enter na klawiaturze lub naciska środkowy przycisk na padzie kierunkowym.
Może się zdarzyć, że jeden element interfejsu użytkownika będzie służył do wprowadzania danych wejściowych, a drugi – ułatwienia dostępu. Ułatwienia dostępu mają umożliwiać usługom ułatwień dostępu metodę interakcji z widocznymi elementami na ekranie, niezależnie od tego, czy z perspektywy systemu można je uaktywniać w postaci danych wejściowych. Aby mieć pewność, że usługa ułatwień dostępu będzie prawidłowo współdziałać z elementami wejściowymi aplikacji, postępuj zgodnie z wytycznymi na temat testowania ułatwień dostępu w aplikacji, by przetestować usługę podczas korzystania z typowej aplikacji.
Usługa ułatwień dostępu może za pomocą metody AccessibilityNodeInfo.findFocus()
określić, który element interfejsu użytkownika ma ukierunkowanie na dane wejściowe lub który jest ukierunkowany na ułatwienia dostępu. Możesz też wyszukiwać elementy, które można wybierać ze względu na dane wejściowe, za pomocą metody focusSearch()
. Usługa ułatwień dostępu może też ustawić fokus przy użyciu metody performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS)
.
Zbieranie informacji
Usługi ułatwień dostępu korzystają z standardowych metod gromadzenia i reprezentowania kluczowych jednostek informacji przekazywanych przez użytkowników, takich jak szczegóły wydarzeń, tekst i liczby.
Pobieranie szczegółów zmiany okna
Android 9 (poziom interfejsu API 28) i nowsze umożliwiają aplikacjom śledzenie aktualizacji okien, gdy aplikacja ponownie pobiera wiele okien jednocześnie. W przypadku zdarzenia TYPE_WINDOWS_CHANGED
użyj interfejsu API getWindowChanges()
, aby określić sposób zmiany okien. Podczas aktualizacji w trybie wielu okien
każde okno generuje własny zestaw zdarzeń. Metoda getSource()
zwraca widok główny okna powiązanego z każdym zdarzeniem.
Jeśli aplikacja określa tytuły panelu ułatwień dostępu dla swoich obiektów View
, usługa może rozpoznać aktualizację interfejsu aplikacji. Gdy wystąpi zdarzenie TYPE_WINDOW_STATE_CHANGED
, użyj typów zwracanych przez getContentChangeTypes()
, aby określić, jak zmienia się okno. Na przykład platforma może wykryć, że panel ma nowy tytuł lub kiedy znika.
Pobieranie szczegółów wydarzenia
Android udostępnia usługom ułatwień dostępu informacje o interakcji z interfejsem za pomocą obiektów AccessibilityEvent
. W poprzednich wersjach Androida informacje dostępne w związku z ułatwieniami dostępu, a jednocześnie istotne szczegóły na temat ustawień interfejsu wybranych przez użytkowników, oferowały ograniczone informacje kontekstowe. W wielu przypadkach brak informacji kontekstowych może mieć kluczowe znaczenie dla zrozumienia wybranego elementu sterującego.
Przykładem interfejsu, w którym kontekst ma kluczowe znaczenie, jest kalendarz lub plan dnia. Jeśli użytkownik wybierze przedział czasu o godzinie 16:00 na liście dni od poniedziałku do piątku, a usługa ułatwień dostępu wypowie „16:00”, ale nie ogłosi nazwy dnia tygodnia, dnia miesiąca ani miesiąca, wynik będzie mylący. W takiej sytuacji dla użytkownika, który chce zaplanować spotkanie, kluczowy jest kontekst ustawień interfejsu.
Od 2011 roku Android znacznie zwiększa ilość informacji o interakcji z interfejsem, które usługa ułatwień dostępu może uzyskać, tworząc zdarzenia ułatwień dostępu na podstawie hierarchii widoków. Hierarchia widoków to zbiór komponentów interfejsu, które zawierają dany komponent (jego elementy nadrzędne) oraz elementy interfejsu, które mogą zawierać ten komponent (jego elementy podrzędne). Dzięki temu Android może udostępniać więcej szczegółów na temat zdarzeń ułatwień dostępu, a usługi ułatwień dostępu przekazywać użytkownikom bardziej przydatne informacje.
Usługa ułatwień dostępu pobiera informacje o zdarzeniu w interfejsie za pomocą obiektu AccessibilityEvent
przekazywanego przez system do metody wywołania zwrotnego onAccessibilityEvent()
usługi. Ten obiekt udostępnia szczegółowe informacje o zdarzeniu, w tym typ obiektu, na którym wykonuje się działania, jego opis i inne szczegóły.
AccessibilityEvent.getRecordCount()
igetRecord(int)
: te metody pozwalają pobrać zbiór obiektówAccessibilityRecord
, które współtworzą wartośćAccessibilityEvent
przekazaną przez system. Ten poziom szczegółów zapewnia dodatkowy kontekst dla zdarzenia, które uruchamia usługę ułatwień dostępu.AccessibilityRecord.getSource()
: ta metoda zwraca obiektAccessibilityNodeInfo
. Ten obiekt umożliwia żądanie hierarchii układu widoku (elementy nadrzędne i podrzędne) komponentu, który jest źródłem zdarzenia ułatwień dostępu. Ta funkcja umożliwia usłudze ułatwień dostępu badanie pełnego kontekstu zdarzenia, w tym treści i stanu wszystkich widoków towarzyszących lub widoków podrzędnych.
Platforma Android umożliwia usłudze AccessibilityService
wysyłanie zapytań do hierarchii widoków, gromadząc informacje o komponencie interfejsu, który generuje zdarzenie, a także o jego elemencie nadrzędnym i podrzędnym. W tym celu ustaw ten wiersz w konfiguracji XML:
android:canRetrieveWindowContent="true"
Potem pobierz obiekt AccessibilityNodeInfo
za pomocą getSource()
.
To wywołanie zwraca obiekt tylko wtedy, gdy okno, z którego pochodzi zdarzenie, jest nadal aktywne. Jeśli nie, zwraca wartość null, więc musisz się zachować.
W tym przykładzie po otrzymaniu zdarzenia kod wykonuje takie działania:
- Natychmiast pobiera element nadrzędny widoku, w którym zaczyna się zdarzenie.
- W tym widoku szuka etykiety i pola wyboru w widoku treści podrzędnych.
- Jeśli je znajdzie, tworzy ciąg znaków do zgłoszenia użytkownikowi, który wskazuje etykietę i informację, czy została sprawdzona.
Jeśli w którymkolwiek momencie podczas przemierzania hierarchii widoków zostanie zwrócona wartość null, metoda dyskretnie się podda.
Kotlin
// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo. override fun onAccessibilityEvent(event: AccessibilityEvent) { val source: AccessibilityNodeInfo = event.source ?: return // Grab the parent of the view that fires the event. val rowNode: AccessibilityNodeInfo = getListItemNodeInfo(source) ?: return // Using this parent, get references to both child nodes, the label, and the // checkbox. val taskLabel: CharSequence = rowNode.getChild(0)?.text ?: run { rowNode.recycle() return } val isComplete: Boolean = rowNode.getChild(1)?.isChecked ?: run { rowNode.recycle() return } // Determine what the task is and whether it's complete based on the text // inside the label, and the state of the checkbox. if (rowNode.childCount < 2 || !rowNode.getChild(1).isCheckable) { rowNode.recycle() return } val completeStr: String = if (isComplete) { getString(R.string.checked) } else { getString(R.string.not_checked) } val reportStr = "$taskLabel$completeStr" speakToUser(reportStr) }
Java
// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo. @Override public void onAccessibilityEvent(AccessibilityEvent event) { AccessibilityNodeInfo source = event.getSource(); if (source == null) { return; } // Grab the parent of the view that fires the event. AccessibilityNodeInfo rowNode = getListItemNodeInfo(source); if (rowNode == null) { return; } // Using this parent, get references to both child nodes, the label, and the // checkbox. AccessibilityNodeInfo labelNode = rowNode.getChild(0); if (labelNode == null) { rowNode.recycle(); return; } AccessibilityNodeInfo completeNode = rowNode.getChild(1); if (completeNode == null) { rowNode.recycle(); return; } // Determine what the task is and whether it's complete based on the text // inside the label, and the state of the checkbox. if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) { rowNode.recycle(); return; } CharSequence taskLabel = labelNode.getText(); final boolean isComplete = completeNode.isChecked(); String completeStr = null; if (isComplete) { completeStr = getString(R.string.checked); } else { completeStr = getString(R.string.not_checked); } String reportStr = taskLabel + completeStr; speakToUser(reportStr); }
Dzięki temu zyskujesz kompletną, działającą usługę ułatwień dostępu. Spróbuj skonfigurować sposób jego interakcji z użytkownikiem – dodaj mechanizm zamiany tekstu na mowę na Androidzie lub użyj narzędzia Vibrator
do sygnalizowania reakcji haptycznych.
Przetwórz tekst
Urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym są wyposażone w kilka funkcji przetwarzania tekstu, które ułatwiają usługom ułatwień dostępu rozpoznawanie i obsługę określonych jednostek tekstu pojawiających się na ekranie.
Etykietki
Android 9 (poziom interfejsu API 28) wprowadza kilka funkcji zapewniających dostęp do etykietek w interfejsie aplikacji. Użyj polecenia
getTooltipText()
,
aby odczytać tekst etykietki, oraz za pomocą
ACTION_SHOW_TOOLTIP
i
ACTION_HIDE_TOOLTIP
,
aby przekazać instrukcjom View
ich wyświetlanie lub ukrywanie.
Tekst podpowiedzi
Od 2017 roku Android udostępnia kilka metod interakcji z tekstem podpowiedzi obiektu opartego na tekście:
- Metody
isShowingHintText()
isetShowingHintText()
wskazują i ustawiają odpowiednio, czy bieżąca zawartość tekstowa węzła reprezentuje tekst podpowiedzi węzła. getHintText()
zapewnia dostęp do tekstu podpowiedzi. Nawet jeśli obiekt nie wyświetla tekstu podpowiedzi, wywołaniegetHintText()
powiodło się.
Lokalizacje znaków tekstu na ekranie
Na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym usługi ułatwień dostępu mogą określać współrzędne ekranu dla każdej ramki ograniczającej widoczny znak w widżecie TextView
. Usługi mogą znaleźć te współrzędne, wywołując metodę refreshWithExtraData()
i przekazując EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
jako pierwszy argument, a obiekt Bundle
jako drugi. Podczas wykonywania metody system wypełnia argument Bundle
tablicą obiektów Rect
z możliwością parcelacji. Każdy obiekt Rect
reprezentuje ramkę ograniczającą określonego znaku.
Ujednolicone jednostronne wartości zakresu
Niektóre obiekty AccessibilityNodeInfo
używają instancji AccessibilityNodeInfo.RangeInfo
, która wskazuje, że element interfejsu może przyjmować zakres wartości. Podczas tworzenia zakresu za pomocą RangeInfo.obtain()
lub pobierania skrajnych wartości zakresu za pomocą metod getMin()
i getMax()
pamiętaj, że urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym reprezentują zakresy jednostronne w ustandaryzowany sposób:
- W zakresach bez wartości minimalnej
Float.NEGATIVE_INFINITY
reprezentuje wartość minimalną. - W przypadku zakresów bez wartości maksymalnej
Float.POSITIVE_INFINITY
reprezentuje wartość maksymalną.
Odpowiadanie na zdarzenia związane z ułatwieniami dostępu
Teraz gdy usługa jest skonfigurowana do uruchamiania i nasłuchiwania zdarzeń, napisz kod, aby wiedział, co zrobić, gdy nadejdzie AccessibilityEvent
. Zacznij od zastąpienia metody onAccessibilityEvent(AccessibilityEvent)
. W tej metodzie użyj metody getEventType()
do określenia typu zdarzenia oraz getContentDescription()
, aby wyodrębnić tekst etykiety powiązany z widokiem, który uruchamia zdarzenie:
Kotlin
override fun onAccessibilityEvent(event: AccessibilityEvent) { var eventText: String = when (event.eventType) { AccessibilityEvent.TYPE_VIEW_CLICKED -> "Clicked: " AccessibilityEvent.TYPE_VIEW_FOCUSED -> "Focused: " else -> "" } eventText += event.contentDescription // Do something nifty with this text, like speak the composed string back to // the user. speakToUser(eventText) ... }
Java
@Override public void onAccessibilityEvent(AccessibilityEvent event) { final int eventType = event.getEventType(); String eventText = null; switch(eventType) { case AccessibilityEvent.TYPE_VIEW_CLICKED: eventText = "Clicked: "; break; case AccessibilityEvent.TYPE_VIEW_FOCUSED: eventText = "Focused: "; break; } eventText = eventText + event.getContentDescription(); // Do something nifty with this text, like speak the composed string back to // the user. speakToUser(eventText); ... }
Dodatkowe materiały
Więcej informacji znajdziesz w tych materiałach: