Tworzenie własnej usługi ułatwień dostępu (widoki)

Pojęcia i implementacja w Jetpack Compose

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. Na przykład użytkownicy, którzy prowadzą samochód, opiekują się małym dzieckiem lub uczestniczą w głośnej imprezie, mogą potrzebować dodatkowych lub alternatywnych informacji zwrotnych z interfejsu.

Android udostępnia standardowe usługi ułatwień dostępu, w tym TalkBack, a deweloperzy mogą tworzyć i dystrybuować własne usługi. W tym dokumencie wyjaśniamy podstawy tworzenia usługi ułatwień dostępu.

Usługa ułatwień dostępu może być dołączona do zwykłej aplikacji lub utworzona jako osobny projekt aplikacji na Androida. Procedura tworzenia usługi jest taka sama w obu przypadkach.

Tworzenie usługi ułatwień dostępu

W projekcie utwórz klasę, która rozszerza 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 na potrzeby tego Service utworzysz nowy projekt i nie planujesz powiązać z nim aplikacji, możesz usunąć z kodu źródłowego klasę początkową Activity.

Deklaracje i uprawnienia w pliku manifestu

Aplikacje, które udostępniają usługi ułatwień dostępu, muszą zawierać w swoich plikach manifestu określone deklaracje, aby system Android traktował je jako usługi ułatwień dostępu. W tej sekcji opisujemy wymagane i opcjonalne ustawienia usług ułatwień dostępu.

Deklaracja dotycząca usług ułatwień dostępu

Aby aplikacja była traktowana jako usługa ułatwień dostępu, w pliku manifestu w elemencie application umieść element service, a nie element activity. Dodatkowo w elemencie service umieść filtr intencji usługi ułatwień dostępu. Plik manifestu musi też chronić usługę, dodając uprawnienie BIND_ACCESSIBILITY_SERVICE, aby można było z nią powiązać tylko system. 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ą udostępniać konfigurację, która określa typy zdarzeń ułatwień dostępu obsługiwanych przez usługę, oraz dodatkowe informacje o usłudze. Konfiguracja usługi ułatwień dostępu znajduje się w klasie AccessibilityServiceInfo. Usługa może tworzyć i ustawiać konfigurację za pomocą instancji tej klasy i setServiceInfo() w czasie działania. Jednak nie wszystkie opcje konfiguracji są dostępne w tej metodzie.

W pliku manifestu możesz umieścić element <meta-data> z odwołaniem do pliku konfiguracyjnego, który umożliwia ustawienie pełnego zakresu opcji 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>. Poniższy kod zawiera przykład zawartości 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 tych dokumentach referencyjnych:

Więcej informacji o tym, które ustawienia konfiguracji można dynamicznie ustawiać w czasie działania, znajdziesz w dokumentacji referencyjnej AccessibilityServiceInfo.

Konfigurowanie usługi ułatwień dostępu

Podczas ustawiania zmiennych konfiguracji usługi ułatwień dostępu, aby określić, jak i kiedy ma ona działać, weź pod uwagę te kwestie:

  • Na jakie typy zdarzeń ma reagować?
  • Czy usługa ma być aktywna we wszystkich aplikacjach, czy tylko w przypadku określonych nazw pakietów?
  • Jakich typów opinii używa?

Te zmienne możesz ustawić na 2 sposoby. Opcja zapewniająca zgodność wsteczną polega na ustawieniu ich w kodzie za pomocą elementu setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo). Aby to zrobić, zastąp metodę onServiceConnected() i skonfiguruj w niej usługę, jak pokazano 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, np. canRetrieveWindowContent, są dostępne tylko wtedy, gdy skonfigurujesz usługę za pomocą XML. Opcje konfiguracji z poprzedniego przykładu zdefiniowane za pomocą kodu XML wyglądają tak:

<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 res/xml/serviceconfig.xml, nowy tag będzie 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ług ułatwień dostępu

Usługa ułatwień dostępu musi rozszerzać klasę AccessibilityService i zastępować te metody z tej klasy: Metody te są przedstawione w kolejności, w jakiej wywołuje je system Android: od momentu uruchomienia usługi (onServiceConnected()) przez czas jej działania (onAccessibilityEvent(), onInterrupt()) aż do jej wyłączenia (onUnbind()).

  • onServiceConnected(): (opcjonalnie) system wywołuje tę metodę, gdy łączy się z usługą ułatwień dostępu. Użyj tej metody, aby wykonać jednorazowe kroki konfiguracji usługi, w tym połączyć się z usługami systemu opinii użytkowników, takimi jak menedżer dźwięku lub wibrator urządzenia. Jeśli chcesz skonfigurować usługę w czasie działania lub wprowadzić jednorazowe zmiany, możesz wygodnie wywołać funkcję setServiceInfo().

  • onAccessibilityEvent(): (wymagane) system wywołuje tę metodę, gdy wykryje AccessibilityEvent, które pasuje do parametrów filtrowania zdarzeń określonych przez usługę ułatwień dostępu, np. gdy użytkownik kliknie przycisk lub skupi się na kontrolce interfejsu w aplikacji, w której usługa ułatwień dostępu przekazuje informacje zwrotne. Gdy system wywołuje tę metodę, przekazuje powiązany obiekt AccessibilityEvent, który usługa może następnie zinterpretować i wykorzystać do przekazania użytkownikowi informacji zwrotnych. Tę metodę można wywoływać wiele razy w trakcie cyklu życia usługi.

  • onInterrupt(): (wymagane) system wywołuje tę metodę, gdy chce przerwać przekazywanie opinii przez usługę, zwykle w odpowiedzi na działanie użytkownika, takie jak przeniesienie fokusu na inny element sterujący. Tę metodę można wywoływać wiele razy w trakcie cyklu życia usługi.

  • onUnbind(): (opcjonalnie) system wywołuje tę metodę, gdy ma zamiar wyłączyć usługę ułatwień dostępu. Użyj tej metody, aby wykonać jednorazowe procedury zamykania, w tym zwolnić usługi systemu opinii użytkowników, takie jak menedżer dźwięku lub wibrator urządzenia.

Te metody wywołania zwrotnego zapewniają podstawową strukturę usługi ułatwień dostępu. Możesz zdecydować, jak przetwarzać dane dostarczane przez system Android w postaci obiektów AccessibilityEvent, i przekazywać użytkownikowi informacje zwrotne. Więcej informacji o uzyskiwaniu informacji ze zdarzenia ułatwień dostępu znajdziesz w artykule Uzyskiwanie szczegółów zdarzenia.

Rejestrowanie się na wydarzenia związane z ułatwieniami dostępu

Jedną z najważniejszych funkcji parametrów konfiguracji usługi ułatwień dostępu jest możliwość określenia typów zdarzeń związanych z ułatwieniami dostępu, które może obsługiwać Twoja usługa. Podanie tych informacji umożliwia współpracę usługom ułatwień dostępu i daje Ci elastyczność w zakresie obsługi tylko określonych typów zdarzeń z określonych aplikacji. Filtrowanie zdarzeń może obejmować te kryteria:

  • Nazwy pakietów: podaj nazwy pakietów aplikacji, których zdarzenia związane z ułatwieniami dostępu ma obsługiwać Twoja usługa. Jeśli ten parametr zostanie pominięty, usługa ułatwień dostępu będzie uznawana za dostępną do obsługi zdarzeń ułatwień dostępu w przypadku dowolnej aplikacji. Ten parametr możesz ustawić w plikach konfiguracyjnych usługi ułatwień dostępu za pomocą atrybutu android:packageNames jako listy rozdzielonej przecinkami lub użyć elementu AccessibilityServiceInfo.packageNames.

  • Typy zdarzeń: określ typy zdarzeń związanych z ułatwieniami dostępu, które ma obsługiwać Twoja usługa. Ten parametr możesz ustawić w plikach konfiguracyjnych usługi ułatwień dostępu za pomocą atrybutu android:accessibilityEventTypes jako listy rozdzielonej znakiem |, np. accessibilityEventTypes="typeViewClicked|typeViewFocused". Możesz też ustawić go za pomocą elementu AccessibilityServiceInfo.eventTypes.

Podczas konfigurowania usługi ułatwień dostępu dokładnie zastanów się, jakie zdarzenia może ona obsługiwać, i zarejestruj się tylko w przypadku tych zdarzeń. Użytkownicy mogą aktywować więcej niż jedną usługę ułatwień dostępu naraz, więc Twoja usługa nie może wykorzystywać zdarzeń, których nie jest w stanie obsłużyć. Pamiętaj, że inne usługi mogą obsługiwać te zdarzenia, aby zwiększyć wygodę użytkowników.

Głośność przy ułatwieniach dostępu

Urządzenia z Androidem 8.0 (poziom interfejsu API 26) i nowszym mają kategorię głośności STREAM_ACCESSIBILITY, która umożliwia sterowanie głośnością wyjścia audio usługi ułatwień dostępu niezależnie od innych dźwięków na urządzeniu.

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() na instancji AudioManager urządzenia.

Ten fragment kodu pokazuje, jak usługa ułatwień dostępu może używać 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 na Androidzie z konferencji Google I/O 2017 (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ć preferowane usługi ułatwień dostępu na dowolnym ekranie, naciskając i przytrzymując jednocześnie oba przyciski głośności. Chociaż ten skrót domyślnie włącza i wyłącza TalkBack, użytkownicy mogą skonfigurować przycisk tak, aby włączał i wyłączał dowolną usługę zainstalowaną na 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 poprosić o tę funkcję w czasie działania.

Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu na Androidzie z konferencji Google I/O 2017, od 13:25.

Przycisk ułatwień dostępu

Na urządzeniach z obszarem nawigacji renderowanym przez oprogramowanie i Androidem 8.0 (poziom interfejsu API 26) lub nowszym po prawej stronie paska nawigacyjnego znajduje się przycisk ułatwień dostępu. Gdy użytkownicy naciśną ten przycisk, mogą wywołać jedną z kilku włączonych funkcji i usług ułatwień dostępu, w zależności od treści wyświetlanych 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 następnie rejestrować wywołania zwrotne za pomocą funkcji registerAccessibilityButtonCallback().

Ten fragment kodu pokazuje, jak skonfigurować usługę ułatwień dostępu, aby reagowała 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 na Androidzie z konferencji 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) i nowszym mogą reagować na przesunięcia w różnych kierunkach (w górę, w dół, w lewo i w prawo) wzdłuż czytnika linii papilarnych urządzenia. Aby skonfigurować usługę do odbierania wywołań zwrotnych dotyczących tych interakcji, wykonaj te czynności:

  1. Zadeklaruj uprawnienie USE_BIOMETRIC i możliwość CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES.
  2. Ustaw flagę FLAG_REQUEST_FINGERPRINT_GESTURES w atrybucie android:accessibilityFlags.
  3. Zarejestruj wywołania zwrotne za pomocą registerFingerprintGestureCallback().

Pamiętaj, że nie wszystkie urządzenia mają czytniki linii papilarnych. Aby sprawdzić, czy urządzenie obsługuje czujnik, użyj metody isHardwareDetected(). Nawet na urządzeniu z czytnikiem linii papilarnych usługa nie może korzystać z tego czytnika, gdy jest on używany do uwierzytelniania. Aby określić, kiedy czujnik jest dostępny, wywołaj metodę isGestureDetectionAvailable() i zastosuj wywołanie zwrotne onGestureDetectionAvailabilityChanged().

Poniższy fragment kodu pokazuje przykład użycia gestów odciskiem palca do poruszania się po wirtualnej planszy do gry:

// 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 na Androidzie z konferencji Google I/O 2017, który zaczyna się o 9:03.

Wielojęzyczna zamiana tekstu na mowę

Począwszy od Androida 8.0 (interfejs API na poziomie 26), usługa zamiany tekstu na mowę (TTS) na Androidzie może rozpoznawać i wypowiadać frazy w wielu językach w ramach jednego bloku tekstu. Aby włączyć tę funkcję automatycznego przełączania języka w usłudze ułatwień dostępu, umieść wszystkie ciągi tekstowe w obiektach LocaleSpan, jak pokazano w tym fragmencie kodu:

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 Co nowego w ułatwieniach dostępu na Androidzie z konferencji Google I/O 2017 (od 10:59).

Działanie w imieniu użytkowników

Od 2011 r. usługi ułatwień dostępu mogą działać w imieniu użytkowników, w tym zmieniać fokus wprowadzania i wybierać (aktywować) elementy interfejsu. W 2012 r. zakres działań został rozszerzony o przewijanie list i interakcje z polami tekstowymi. Usługi ułatwień dostępu mogą też wykonywać działania globalne, takie jak przechodzenie na ekran główny, naciskanie przycisku Wstecz oraz otwieranie ekranu powiadomień i listy ostatnio używanych aplikacji. Od 2012 roku Android ma fokus ułatwień dostępu, który sprawia, że wszystkie widoczne elementy można wybrać za pomocą usługi ułatwień dostępu.

Te możliwości pozwalają deweloperom usług ułatwień dostępu tworzyć alternatywne tryby nawigacji, takie jak nawigacja przy użyciu gestów, i zapewniają użytkownikom z niepełnosprawnościami większą kontrolę nad urządzeniami z Androidem.

Nasłuchiwanie gestów

Usługi ułatwień dostępu mogą nasłuchiwać określonych gestów i reagować na nie, wykonując działania w imieniu użytkownika. Ta funkcja wymaga, aby usługa ułatwień dostępu poprosiła o aktywację funkcji Czytanie dotykiem. Usługa może poprosić o tę aktywację, ustawiając element flags instancji usługi AccessibilityServiceInfo 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 usługa poprosi o aktywację funkcji Czytanie dotykiem, użytkownik musi zezwolić na jej włączenie, jeśli nie jest ona jeszcze aktywna. Gdy ta funkcja jest aktywna, Twoja usługa otrzymuje powiadomienia o gestach ułatwień dostępu za pomocą metody wywołania zwrotnego onGesture() i może odpowiadać, wykonując działania w imieniu użytkownika.

Ciągłe gesty

Urządzenia z Androidem 8.0 (poziom interfejsu API 26) i nowszym obsługują gesty ciągłe, czyli gesty programowe zawierające więcej niż 1 Path obiekt.

Podczas określania sekwencji pociągnięć możesz wskazać, że należą one do tego samego gestu programowego, używając argumentu końcowego willContinue w konstruktorze GestureDescription.StrokeDescription, jak pokazano 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 na Androidzie z konferencji Google I/O 2017 (od 15:47).

Korzystanie z działań związanych z ułatwieniami dostępu

Usługi ułatwień dostępu mogą działać w imieniu użytkowników, aby uprościć interakcje z aplikacjami i zwiększyć produktywność. Możliwość wykonywania działań przez usługi ułatwień dostępu została dodana w 2011 roku i znacznie rozszerzona w 2012 roku.

Aby działać w imieniu użytkowników, usługa ułatwień dostępu musi zarejestrować się, aby otrzymywać zdarzenia z aplikacji, i poprosić o uprawnienia do wyświetlania treści aplikacji, ustawiając wartość android:canRetrieveWindowContent na truepliku konfiguracyjnym usługi. Gdy usługa otrzyma zdarzenia, może pobrać z nich obiekt AccessibilityNodeInfo za pomocą funkcji getSource(). Za pomocą obiektu AccessibilityNodeInfo usługa może następnie zbadać hierarchię widoków, aby określić, jakie działanie należy podjąć, a potem wykonać je w imieniu użytkownika za pomocą 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 wykonywanie działań w aplikacji. Jeśli usługa musi wykonać działanie globalne, np. przejść do ekranu głównego, nacisnąć przycisk Wstecz lub otworzyć ekran powiadomień albo listę ostatnich aplikacji, użyj metody performGlobalAction().

Typy ostrości

W 2012 r. w Androidzie wprowadzono fokus interfejsu o nazwie fokus ułatwień dostępu. Usługi ułatwień dostępu mogą używać tego fokusu do wybierania dowolnego widocznego elementu interfejsu użytkownika i wykonywania na nim działań. Ten typ fokusu różni się od fokusu wejściowego, który określa, który element interfejsu użytkownika na ekranie odbiera dane wejściowe, gdy użytkownik wpisuje znaki, naciska Enter na klawiaturze lub naciska środkowy przycisk pada kierunkowego.

Może się zdarzyć, że jeden element interfejsu użytkownika ma fokus wejściowy, a inny element ma fokus ułatwień dostępu. Celem fokusu ułatwień dostępu jest zapewnienie usługom ułatwień dostępu metody interakcji z widocznymi elementami na ekranie, niezależnie od tego, czy element jest z perspektywy systemu elementem, na którym można ustawić fokus. Aby mieć pewność, że usługa ułatwień dostępu wchodzi w interakcje z elementami wejściowymi aplikacji w prawidłowy sposób, postępuj zgodnie z wytycznymi dotyczącymi testowania ułatwień dostępu w aplikacji i przetestuj usługę podczas korzystania z typowych aplikacji.

Usługa ułatwień dostępu może określić, który element interfejsu użytkownika ma fokus wejściowy lub fokus ułatwień dostępu, za pomocą metody AccessibilityNodeInfo.findFocus(). Możesz też wyszukać elementy, które można wybrać za pomocą fokusu wejściowego, używając metody focusSearch(). Usługa ułatwień dostępu może też ustawić fokus ułatwień dostępu za pomocą metody performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS).

Zbieranie informacji

Usługi ułatwień dostępu mają standardowe metody zbierania i przedstawiania kluczowych jednostek informacji przekazywanych przez użytkowników, takich jak szczegóły wydarzeń, tekst i liczby.

Pobieranie szczegółów zmiany okna

Android 9 (poziom 28 interfejsu API) i nowsze wersje umożliwiają aplikacjom śledzenie aktualizacji okien, gdy aplikacja ponownie rysuje wiele okien jednocześnie. Gdy wystąpi zdarzenie TYPE_WINDOWS_CHANGED, użyj interfejsu getWindowChanges() API, aby określić, jak zmieniają się okna. Podczas aktualizacji w wielu oknach każde okno generuje własny zestaw zdarzeń. Metoda getSource() zwraca widok główny okna powiązanego z każdym zdarzeniem.

Jeśli aplikacja definiuje tytuły paneli ułatwień dostępu dla swoich obiektów View, Twoja usługa może rozpoznawać, kiedy interfejs aplikacji jest aktualizowany. 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 framework może wykryć, kiedy panel ma nowy tytuł lub kiedy znika.

Wyświetlanie szczegółów wydarzenia

Android przekazuje informacje o interakcjach z interfejsem użytkownika do usług ułatwień dostępu za pomocą obiektów AccessibilityEvent. W poprzednich wersjach Androida informacje dostępne w zdarzeniu związanym z ułatwieniami dostępu, choć zawierały istotne szczegóły dotyczące elementu interfejsu użytkownika wybranego przez użytkowników, miały ograniczony kontekst. W wielu przypadkach brakujące informacje o kontekście mogą mieć kluczowe znaczenie dla zrozumienia znaczenia wybranego elementu sterującego.

Przykładem interfejsu, w którym kontekst ma kluczowe znaczenie, jest kalendarz lub planer dnia. Jeśli użytkownik wybierze przedział czasowy 16:00 na liście dni od poniedziałku do piątku, a usługa ułatwień dostępu odczyta „16:00”, ale nie odczyta nazwy dnia tygodnia, dnia miesiąca ani nazwy miesiąca, wynikowa opinia będzie niejasna. W tym przypadku kontekst elementu sterującego interfejsu jest kluczowy dla użytkownika, który chce zaplanować spotkanie.

Od 2011 r. Android znacznie zwiększa ilość informacji, które usługa ułatwień dostępu może uzyskać o interakcji z interfejsem użytkownika, 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ą być zawarte w tym komponencie (jego elementy podrzędne). W ten sposób Android może dostarczać więcej szczegółów o zdarzeniach związanych z ułatwieniami dostępu, co pozwala usługom ułatwień dostępu przekazywać użytkownikom bardziej przydatne informacje zwrotne.

Usługa ułatwień dostępu otrzymuje informacje o zdarzeniu interfejsu użytkownika za pomocą obiektu AccessibilityEvent przekazywanego przez system do metody wywołania zwrotnego onAccessibilityEvent() usługi. Ten obiekt zawiera szczegółowe informacje o wydarzeniu, w tym typ obiektu, którego dotyczy działanie, tekst opisowy i inne szczegóły.

  • AccessibilityEvent.getRecordCount()getRecord(int): te metody umożliwiają pobranie zestawu obiektów AccessibilityRecord, które mają wpływ na wartość AccessibilityEvent przekazaną przez system. Ten poziom szczegółowości zapewnia więcej kontekstu dla zdarzenia, które wywołuje usługę ułatwień dostępu.

  • AccessibilityRecord.getSource(): ta metoda zwraca obiekt AccessibilityNodeInfo. Ten obiekt umożliwia wysyłanie zapytań o hierarchię układu widoku (elementy nadrzędne i podrzędne) komponentu, z którego pochodzi zdarzenie związane z ułatwieniami dostępu. Ta funkcja umożliwia usłudze ułatwień dostępu zbadanie pełnego kontekstu zdarzenia, w tym treści i stanu wszystkich widoków nadrzędnych lub podrzędnych.

Platforma Android umożliwia AccessibilityService wysyłanie zapytań do hierarchii widoków i zbieranie informacji o komponencie interfejsu, który generuje zdarzenie, a także o jego elementach nadrzędnych i podrzędnych. Aby to zrobić, w konfiguracji XML ustaw ten wiersz:

android:canRetrieveWindowContent="true"

Gdy to zrobisz, uzyskaj obiekt AccessibilityNodeInfo za pomocą getSource(). Ta funkcja zwraca obiekt tylko wtedy, gdy okno, z którego pochodzi zdarzenie, jest nadal aktywne. Jeśli nie, zwraca wartość null, więc postępuj odpowiednio.

W przykładzie poniżej kod wykonuje te czynności po otrzymaniu zdarzenia:

  1. Natychmiast pobiera element nadrzędny widoku, z którego pochodzi zdarzenie.
  2. W tym widoku poszukaj etykiety i pola wyboru jako widoków podrzędnych.
  3. Jeśli znajdzie takie elementy, utworzy ciąg znaków, który zostanie wyświetlony użytkownikowi. Będzie on zawierać etykietę i informację o tym, czy element został zaznaczony.

Jeśli w dowolnym momencie podczas przechodzenia przez hierarchię widoków zostanie zwrócona wartość null, metoda cicho się wycofa.

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);
}

Masz teraz kompletną, działającą usługę ułatwień dostępu. Spróbuj skonfigurować sposób interakcji z użytkownikiem, dodając mechanizm zamiany tekstu na mowę na Androidzie lub używając Vibrator do przekazywania reakcji haptycznych.

Tekst procesu

Urządzenia z Androidem 8.0 (poziom interfejsu API 26) i nowszym mają kilka funkcji przetwarzania tekstu, które ułatwiają usługom ułatwień dostępu identyfikowanie i obsługiwanie konkretnych jednostek tekstu wyświetlanych na ekranie.

Etykietki

Android 9 (poziom 28 interfejsu API) wprowadza kilka funkcji, które umożliwiają dostęp do dymków w interfejsie aplikacji. Użyj getTooltipText(), aby odczytać tekst etykietki, oraz ACTION_SHOW_TOOLTIPACTION_HIDE_TOOLTIP, aby poinstruować instancje View, aby wyświetlały lub ukrywały etykietki.

Tekst podpowiedzi

Od 2017 roku Android udostępnia kilka metod interakcji z tekstem podpowiedzi obiektu tekstowego:

  • Metody isShowingHintText()setShowingHintText() wskazują odpowiednio, czy bieżąca treść tekstowa węzła reprezentuje tekst podpowiedzi węzła, i ustawiają tę wartość.
  • getHintText() zapewnia dostęp do samego tekstu podpowiedzi. Nawet jeśli obiekt nie wyświetla tekstu podpowiedzi, wywołanie funkcji getHintText() zakończy się powodzeniem.

Lokalizacje znaków tekstowych na ekranie

Na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) i nowszym usługi ułatwień dostępu mogą określać współrzędne ekranu dla każdego widocznego znaku w ramce ograniczającej w widżecie TextView. Usługi znajdują te współrzędne, wywołując funkcję refreshWithExtraData(), przekazując EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY jako pierwszy argument i obiekt Bundle jako drugi argument. Podczas wykonywania metody system wypełnia argument Bundle tablicą obiektów Rect, które można przekazywać między procesami. Każdy obiekt Rect reprezentuje ramkę ograniczającą konkretnego znaku.

Ustandaryzowane wartości zakresu jednostronnego

Niektóre obiekty AccessibilityNodeInfo używają instancji AccessibilityNodeInfo.RangeInfo, aby wskazać, że element interfejsu może przyjmować zakres wartości. Podczas tworzenia zakresu za pomocą RangeInfo.obtain() lub pobierania wartości skrajnych zakresu za pomocą getMin()getMax() pamiętaj, że urządzenia z Androidem 8.0 (poziom 26 interfejsu API) i nowszym reprezentują zakresy jednostronne w standardowy sposób:

Odpowiadanie na zdarzenia związane z ułatwieniami dostępu

Usługa jest już skonfigurowana do działania i nasłuchiwania zdarzeń. Napisz teraz kod, aby wiedziała, co robić, gdy nadejdzie AccessibilityEvent. Zacznij od zastąpienia metody onAccessibilityEvent(AccessibilityEvent). W tej metodzie użyj getEventType(), aby określić typ zdarzenia, oraz getContentDescription(), aby wyodrębnić tekst etykiety powiązany z widokiem, który wywołuje 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:

Przewodniki

Codelabs