Создайте собственную службу обеспечения доступности (Views).

Концепции и реализация Jetpack Compose

Сервис обеспечения доступности — это приложение, которое улучшает пользовательский интерфейс, чтобы помочь пользователям с ограниченными возможностями или тем, кто временно не может полноценно взаимодействовать с устройством. Например, пользователям, которые находятся за рулем, ухаживают за маленьким ребенком или находятся на очень шумной вечеринке, может потребоваться дополнительная или альтернативная обратная связь интерфейса.

Android предоставляет стандартные службы специальных возможностей, включая TalkBack , и разработчики могут создавать и распространять собственные службы. В этом документе объясняются основы создания службы специальных возможностей.

Сервис специальных возможностей можно включить в состав обычного приложения или создать как отдельный проект Android. Шаги по созданию сервиса одинаковы в обоих случаях.

Создайте свою службу обеспечения доступности.

В рамках вашего проекта создайте класс, который наследует AccessibilityService :

Котлин

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() {
    }

...
}

Если вы создаёте новый проект для этой Service и не планируете связывать с ним приложение, вы можете удалить начальный класс Activity из исходного кода.

Манифестные декларации и разрешения

Приложения, предоставляющие услуги специальных возможностей, должны включать в свои манифесты специальные объявления, чтобы система Android рассматривала их как услуги специальных возможностей. В этом разделе описаны обязательные и необязательные настройки для служб специальных возможностей.

Заявление о доступности услуг

Чтобы ваше приложение рассматривалось как служба специальных возможностей, включите в манифест элемент service (а не элемент activity ) внутри элемента application . Кроме того, внутри элемента service добавьте фильтр намерений службы специальных возможностей. Манифест также должен защищать службу, добавив разрешение BIND_ACCESSIBILITY_SERVICE , чтобы гарантировать, что только система может к ней подключаться. Вот пример:

  <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>

настройка службы доступности

Службы специальных возможностей должны предоставлять конфигурацию, определяющую типы событий специальных возможностей, которые обрабатывает служба, а также дополнительную информацию о службе. Конфигурация службы специальных возможностей содержится в классе AccessibilityServiceInfo . Ваша служба может создавать и устанавливать конфигурацию, используя экземпляр этого класса и setServiceInfo() во время выполнения. Однако не все параметры конфигурации доступны с помощью этого метода.

В манифест можно включить элемент <meta-data> со ссылкой на файл конфигурации, что позволит задать полный набор параметров для вашей службы специальных возможностей, как показано в следующем примере:

<service android:name=".MyAccessibilityService">
  ...
  <meta-data
    android:name="android.accessibilityservice"
    android:resource="@xml/accessibility_service_config" />
</service>

Элемент <meta-data> ссылается на XML-файл, который вы создаете в каталоге ресурсов вашего приложения: <project_dir>/res/xml/accessibility_service_config.xml> . Следующий код показывает пример содержимого файла конфигурации службы:

<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"
/>

Для получения дополнительной информации об атрибутах XML, которые можно использовать в файле конфигурации службы специальных возможностей, см. следующую справочную документацию:

Для получения дополнительной информации о том, какие параметры конфигурации могут динамически устанавливаться во время выполнения, см. справочную документацию AccessibilityServiceInfo .

Настройте службу специальных возможностей.

При настройке переменных конфигурации службы специальных возможностей, чтобы указать системе, как и когда ее запускать, учитывайте следующее:

  • На какие типы событий вы хотите, чтобы система реагировала?
  • Необходимо ли активировать эту службу для всех приложений или только для определенных названий пакетов?
  • Какие различные типы обратной связи используются?

У вас есть два варианта установки этих переменных. Вариант с обратной совместимостью — установить их в коде, используя setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) . Для этого переопределите метод ` onServiceConnected() и настройте там свою службу, как показано в следующем примере:

Котлин

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

}

Второй вариант — настроить службу с помощью XML-файла. Некоторые параметры конфигурации, такие как canRetrieveWindowContent , доступны только при настройке службы с использованием XML. Параметры конфигурации из предыдущего примера при определении с помощью 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"
/>

Если вы используете XML, укажите ссылку на него в манифесте, добавив тег <meta-data> к объявлению сервиса, указывающий на XML-файл. Если ваш XML-файл находится в res/xml/serviceconfig.xml , новый тег будет выглядеть следующим образом:

<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>

методы предоставления услуг для людей с ограниченными возможностями

Служба специальных возможностей должна расширять класс AccessibilityService и переопределять следующие методы этого класса. Эти методы представлены в порядке их вызова системой Android: с момента запуска службы ( onServiceConnected() ), во время ее работы ( onAccessibilityEvent() , onInterrupt() ) и до момента ее завершения ( onUnbind() ).

  • onServiceConnected() : (необязательно) система вызывает этот метод при подключении к вашей службе специальных возможностей. Используйте этот метод для выполнения одноразовых шагов настройки вашей службы, включая подключение к службам системы обратной связи с пользователями, таким как менеджер звука или вибратор устройства. Если вы хотите установить конфигурацию вашей службы во время выполнения или внести одноразовые изменения, здесь удобно вызвать setServiceInfo() .

  • onAccessibilityEvent() : (обязательно) Система вызывает этот метод, когда обнаруживает событие AccessibilityEvent , соответствующее параметрам фильтрации событий, указанным вашей службой доступности, например, когда пользователь нажимает кнопку или фокусируется на элементе пользовательского интерфейса в приложении, для которого ваша служба доступности предоставляет обратную связь. При вызове этого метода система передает соответствующее AccessibilityEvent , которое служба затем может интерпретировать и использовать для предоставления обратной связи пользователю. Этот метод может вызываться много раз в течение жизненного цикла вашей службы.

  • onInterrupt() : (обязательный) Этот метод вызывается системой, когда она хочет прервать обратную связь, предоставляемую вашим сервисом, обычно в ответ на действие пользователя, например, перемещение фокуса на другой элемент управления. Этот метод может вызываться много раз в течение жизненного цикла вашего сервиса.

  • onUnbind() : (необязательно) система вызывает этот метод, когда собирается завершить работу службы специальных возможностей. Используйте этот метод для выполнения любых одноразовых процедур завершения работы, включая освобождение ресурсов систем обратной связи с пользователем, таких как менеджер звука или вибратор устройства.

Эти методы обратного вызова обеспечивают базовую структуру для вашей службы специальных возможностей. Вы можете решить, как обрабатывать данные, предоставляемые системой Android в виде объектов AccessibilityEvent , и предоставлять обратную связь пользователю. Для получения дополнительной информации о получении информации из события специальных возможностей см. раздел «Получение сведений о событии» .

Зарегистрируйтесь для участия в мероприятиях, посвященных вопросам доступности.

Одна из важнейших функций параметров конфигурации службы специальных возможностей — это возможность указать, какие типы событий специальных возможностей может обрабатывать ваша служба. Указание этой информации позволяет службам специальных возможностей взаимодействовать друг с другом и дает вам гибкость в обработке только определенных типов событий из определенных приложений. Фильтрация событий может включать следующие критерии:

  • Имена пакетов: укажите имена пакетов приложений, события специальных возможностей которых вы хотите обрабатывать с помощью вашей службы. Если этот параметр опущен, ваша служба специальных возможностей считается доступной для обработки событий специальных возможностей для любого приложения. Вы можете установить этот параметр в файлах конфигурации службы специальных возможностей с помощью атрибута android:packageNames в виде списка, разделенного запятыми, или использовать член AccessibilityServiceInfo.packageNames .

  • Типы событий: укажите типы событий доступности, которые вы хотите, чтобы ваша служба обрабатывала. Вы можете установить этот параметр в файлах конфигурации службы доступности с помощью атрибута android:accessibilityEventTypes в виде списка, разделенного символом | — например, accessibilityEventTypes="typeViewClicked|typeViewFocused" . Или вы можете установить его с помощью члена AccessibilityServiceInfo.eventTypes .

При настройке службы специальных возможностей тщательно продумайте, какие события она может обрабатывать, и регистрируйтесь только для этих событий. Поскольку пользователи могут активировать более одной службы специальных возможностей одновременно, ваша служба не должна обрабатывать события, которые она не может обработать. Помните, что другие службы могут обрабатывать эти события для улучшения пользовательского опыта.

Доступность тома

Устройства под управлением Android 8.0 (уровень API 26) и выше включают категорию громкости STREAM_ACCESSIBILITY , которая позволяет регулировать громкость аудиовыхода службы специальных возможностей независимо от других звуков на устройстве.

Службы специальных возможностей могут использовать этот тип потока, установив параметр FLAG_ENABLE_ACCESSIBILITY_VOLUME . Затем вы можете изменить громкость звука специальных возможностей устройства, вызвав метод adjustStreamVolume() в экземпляре AudioManager устройства.

Следующий фрагмент кода демонстрирует, как служба специальных возможностей может использовать категорию тома STREAM_ACCESSIBILITY :

Котлин

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

Для получения дополнительной информации смотрите видеозапись сессии « Что нового в специальных возможностях Android» с конференции Google I/O 2017, начиная с 6:35.

ярлык доступности

На устройствах под управлением Android 8.0 (уровень API 26) и выше пользователи могут включать и отключать предпочитаемую службу специальных возможностей с любого экрана, одновременно нажав и удерживая обе кнопки регулировки громкости. Хотя этот ярлык по умолчанию включает и отключает Talkback, пользователи могут настроить кнопку для включения и отключения любой службы, установленной на их устройстве.

Для того чтобы пользователи могли получить доступ к определенной службе специальных возможностей с помощью ярлыка специальных возможностей, служба должна запросить эту функцию во время выполнения.

Для получения дополнительной информации смотрите видеозапись сессии « Что нового в специальных возможностях Android» с конференции Google I/O 2017, начиная с 13:25.

Кнопка доступности

На устройствах с программно отображаемой областью навигации и операционной системой Android 8.0 (уровень API 26) и выше, в правой части панели навигации находится кнопка специальных возможностей . При нажатии на эту кнопку пользователи могут активировать одну из нескольких доступных функций и служб специальных возможностей в зависимости от содержимого, отображаемого в данный момент на экране.

Чтобы пользователи могли вызывать определенную службу специальных возможностей с помощью кнопки специальных возможностей, службе необходимо добавить флаг FLAG_REQUEST_ACCESSIBILITY_BUTTON в атрибут android:accessibilityFlags объекта AccessibilityServiceInfo . Затем служба может зарегистрировать обратные вызовы с помощью registerAccessibilityButtonCallback() .

Приведенный ниже фрагмент кода демонстрирует, как можно настроить службу специальных возможностей для реагирования на нажатие пользователем кнопки специальных возможностей:

Котлин

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

Для получения дополнительной информации смотрите видеозапись сессии « Что нового в специальных возможностях Android» с конференции Google I/O 2017, начиная с 16:28.

Жесты отпечатков пальцев

Службы специальных возможностей на устройствах под управлением Android 8.0 (уровень API 26) и выше могут реагировать на движения пальцем (вверх, вниз, влево и вправо) вдоль датчика отпечатков пальцев. Чтобы настроить службу для получения обратных вызовов об этих взаимодействиях, выполните следующую последовательность действий:

  1. Объявите разрешение USE_BIOMETRIC и возможность CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES .
  2. Установите флаг FLAG_REQUEST_FINGERPRINT_GESTURES в атрибуте android:accessibilityFlags .
  3. Зарегистрируйте функцию обратного вызова с помощью registerFingerprintGestureCallback() .

Учтите, что не все устройства оснащены датчиками отпечатков пальцев. Чтобы определить, поддерживает ли устройство этот датчик, используйте метод isHardwareDetected() . Даже на устройстве, оснащенном датчиком отпечатков пальцев, ваша служба не сможет использовать его, если он используется для аутентификации. Чтобы определить, когда датчик доступен, вызовите метод isGestureDetectionAvailable() и реализуйте функцию обратного вызова onGestureDetectionAvailabilityChanged() .

Следующий фрагмент кода демонстрирует пример использования жестов по отпечатку пальца для навигации по виртуальному игровому полю:

// 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"
    ... />

Котлин

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

Для получения дополнительной информации смотрите видеозапись сессии « Что нового в специальных возможностях Android» с конференции Google I/O 2017, начиная с отметки 9:03.

Многоязычный текст в речь

Начиная с Android 8.0 (уровень API 26), служба преобразования текста в речь (TTS) Android может распознавать и произносить фразы на нескольких языках в одном блоке текста. Чтобы включить эту функцию автоматического переключения языка в службе специальных возможностей, оберните все строки в объекты LocaleSpan , как показано в следующем фрагменте кода:

Котлин

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

Для получения дополнительной информации смотрите видеозапись сессии « Что нового в специальных возможностях Android» с конференции Google I/O 2017, начиная с 10:59.

Действовать от имени пользователей

Начиная с 2011 года, службы специальных возможностей могут действовать от имени пользователей, включая изменение фокуса ввода и выбор (активацию) элементов пользовательского интерфейса. В 2012 году диапазон действий расширился и включил прокрутку списков и взаимодействие с текстовыми полями. Службы специальных возможностей также могут выполнять глобальные действия, такие как переход на главный экран, нажатие кнопки «Назад» и открытие экрана уведомлений и списка последних приложений. С 2012 года Android включает функцию «Фокус специальных возможностей» , которая позволяет выбирать все видимые элементы с помощью службы специальных возможностей.

Эти возможности позволяют разработчикам сервисов для людей с ограниченными возможностями создавать альтернативные режимы навигации, такие как навигация жестами, и предоставлять пользователям с инвалидностью улучшенный контроль над своими устройствами на базе Android.

Прислушивайтесь к жестам.

Службы специальных возможностей могут отслеживать определенные жесты и реагировать, действуя от имени пользователя. Для использования этой функции вашей службе специальных возможностей необходимо запросить активацию функции «Исследование с помощью касания». Ваша служба может запросить эту активацию, установив для члена flags экземпляра AccessibilityServiceInfo службы значение FLAG_REQUEST_TOUCH_EXPLORATION_MODE , как показано в следующем примере.

Котлин

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

После того, как ваш сервис запросит активацию функции «Исследование касанием», пользователь должен разрешить включение этой функции, если она еще не активирована. Когда эта функция активна, ваш сервис получает уведомления о жестах доступности через метод обратного вызова onGesture() и может реагировать, выполняя действия от имени пользователя.

Продолжение жестов

Устройства под управлением Android 8.0 (уровень API 26) и выше поддерживают непрерывные жесты , или программные жесты, содержащие более одного объекта Path .

При задании последовательности штрихов можно указать, что они относятся к одному и тому же программному жесту, используя последний аргумент willContinue в конструкторе GestureDescription.StrokeDescription , как показано в следующем фрагменте кода:

Котлин

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

Для получения дополнительной информации смотрите видеозапись сессии « Что нового в специальных возможностях Android» с конференции Google I/O 2017, начиная с 15:47.

Используйте действия обеспечения доступности

Службы обеспечения доступности могут действовать от имени пользователей, упрощая взаимодействие с приложениями и повышая их продуктивность. Возможность выполнения действий службами обеспечения доступности была добавлена ​​в 2011 году и значительно расширена в 2012 году.

Для выполнения действий от имени пользователей ваша служба специальных возможностей должна зарегистрироваться для получения событий от приложений и запросить разрешение на просмотр содержимого приложений, установив параметр android:canRetrieveWindowContent в true в файле конфигурации службы . Когда ваша служба получает события, она может получить объект AccessibilityNodeInfo из события с помощью getSource() . Имея объект AccessibilityNodeInfo , ваша служба может затем изучить иерархию представлений, чтобы определить, какое действие следует предпринять, а затем выполнить действия от имени пользователя с помощью performAction() .

Котлин

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

Метод performAction() позволяет вашей службе выполнять действия внутри приложения. Если вашей службе необходимо выполнить глобальное действие, например, перейти на главный экран, нажать кнопку «Назад» или открыть экран уведомлений или список последних приложений, используйте метод performGlobalAction() .

Используйте типы фокусировки

В 2012 году Android представил функцию фокусировки пользовательского интерфейса, называемую фокусировкой доступности . Службы доступности могут использовать эту фокусировку для выбора любого видимого элемента пользовательского интерфейса и выполнения с ним действий. Этот тип фокусировки отличается от фокусировки ввода , которая определяет, какой элемент пользовательского интерфейса на экране получает ввод, когда пользователь вводит символы, нажимает клавишу Enter на клавиатуре или нажимает центральную кнопку D-pad.

В пользовательском интерфейсе может быть так, что один элемент находится в фокусе ввода, а другой — в фокусе доступности. Цель фокуса доступности — предоставить службам доступности способ взаимодействия с видимыми элементами на экране, независимо от того, является ли этот элемент доступным для фокусировки ввода с точки зрения системы. Чтобы убедиться, что ваша служба доступности корректно взаимодействует с элементами ввода приложений, следуйте рекомендациям по тестированию доступности приложения , протестировав свою службу при использовании обычного приложения.

Служба доступности может определить, какой элемент пользовательского интерфейса находится в фокусе ввода или в фокусе доступности, используя метод AccessibilityNodeInfo.findFocus() . Вы также можете искать элементы, которые можно выбрать с помощью фокуса ввода, используя метод focusSearch() . Наконец, ваша служба доступности может установить фокус доступности с помощью метода performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS) .

Соберите информацию

Службы обеспечения доступности используют стандартные методы сбора и представления ключевых единиц информации, предоставляемой пользователем, таких как сведения о мероприятии, текст и числа.

Получить подробную информацию о замене окна.

В Android 9 (уровень API 28) и выше приложения могут отслеживать обновления окон, когда приложение одновременно перерисовывает несколько окон. При возникновении события TYPE_WINDOWS_CHANGED используйте API getWindowChanges() , чтобы определить, как изменяются окна. Во время обновления нескольких окон каждое окно генерирует свой собственный набор событий. Метод getSource() возвращает корневое представление окна, связанного с каждым событием.

Если приложение определяет заголовки панелей специальных возможностей для своих объектов View , ваша служба сможет распознать обновление пользовательского интерфейса приложения. При возникновении события TYPE_WINDOW_STATE_CHANGED используйте типы, возвращаемые функцией getContentChangeTypes() чтобы определить, как изменяется окно. Например, фреймворк может определить, когда у панели появляется новый заголовок или когда панель исчезает.

Получить подробную информацию о мероприятии

Android предоставляет службам специальных возможностей информацию о взаимодействии с пользовательским интерфейсом через объекты AccessibilityEvent . В предыдущих версиях Android информация, доступная в событии специальных возможностей, хотя и содержала важные сведения о выбранных пользователем элементах управления пользовательского интерфейса, предоставляла ограниченную контекстную информацию. Во многих случаях отсутствие этой контекстной информации может иметь решающее значение для понимания смысла выбранного элемента управления.

Примером интерфейса, где контекст имеет решающее значение, является календарь или планировщик дня. Если пользователь выбирает временной интервал 16:00 в списке дней с понедельника по пятницу, и служба специальных возможностей объявляет «16:00», но не объявляет название дня недели, дня месяца или месяца, полученная обратная связь будет непонятной. В этом случае контекст элемента управления пользовательского интерфейса имеет решающее значение для пользователя, который хочет запланировать встречу.

Начиная с 2011 года, Android значительно расширяет объем информации, которую служба специальных возможностей может получить о взаимодействии с пользовательским интерфейсом, формируя события специальных возможностей на основе иерархии представлений. Иерархия представлений — это набор компонентов пользовательского интерфейса, содержащих данный компонент (его родительские элементы) и элементы пользовательского интерфейса, которые могут содержаться в этом компоненте (его дочерние элементы). Таким образом, Android может предоставлять более подробную информацию о событиях специальных возможностей, позволяя службам специальных возможностей предоставлять пользователям более полезную обратную связь.

Служба специальных возможностей получает информацию о событии пользовательского интерфейса через объект AccessibilityEvent , передаваемый системой в метод обратного вызова onAccessibilityEvent() этой службы. Этот объект предоставляет подробную информацию о событии, включая тип объекта, над которым выполняется действие, его описательный текст и другие детали.

  • Методы AccessibilityEvent.getRecordCount() и getRecord(int) позволяют получить набор объектов AccessibilityRecord , которые участвуют в событии AccessibilityEvent , переданном вам системой. Такой уровень детализации обеспечивает более широкий контекст для события, запускающего вашу службу специальных возможностей.

  • Метод AccessibilityRecord.getSource() возвращает объект AccessibilityNodeInfo . Этот объект позволяет запросить иерархию макета представления (родительские и дочерние элементы) компонента, который инициировал событие доступности. Эта функция позволяет службе доступности исследовать полный контекст события, включая содержимое и состояние любых содержащих его представлений или дочерних представлений.

Платформа Android предоставляет AccessibilityService возможность запрашивать иерархию представлений, собирая информацию о компоненте пользовательского интерфейса, генерирующем событие, а также о его родительском и дочерних элементах. Для этого добавьте следующую строку в конфигурацию XML:

android:canRetrieveWindowContent="true"

После этого получите объект AccessibilityNodeInfo , используя getSource() . Этот вызов возвращает объект только в том случае, если окно, из которого возникло событие, все еще является активным окном. В противном случае он возвращает null, поэтому действуйте соответственно.

В следующем примере код выполняет следующие действия при получении события:

  1. Мгновенно получает родительский элемент представления, где возникло событие.
  2. В этом представлении происходит поиск метки и флажка в качестве дочерних элементов.
  3. Если метка найдена, создается строка для сообщения пользователю, указывающая на метку и на то, была ли она отмечена.

Если в какой-либо момент при обходе иерархии представлений возвращается значение null, метод незаметно завершает работу.

Котлин

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

Теперь у вас есть полноценная, функционирующая служба специальных возможностей. Попробуйте настроить её взаимодействие с пользователем, добавив механизм преобразования текста в речь Android или используя Vibrator для тактильной обратной связи.

Текст процесса

Устройства под управлением Android 8.0 (уровень API 26) и выше включают в себя ряд функций обработки текста, которые упрощают для служб специальных возможностей идентификацию и работу с определенными фрагментами текста, отображаемыми на экране.

Всплывающие подсказки

В Android 9 (уровень API 28) представлен ряд возможностей, позволяющих получить доступ к всплывающим подсказкам в пользовательском интерфейсе приложения. Используйте getTooltipText() для чтения текста всплывающей подсказки, а также ACTION_SHOW_TOOLTIP и ACTION_HIDE_TOOLTIP для указания экземплярам View показывать или скрывать свои всплывающие подсказки.

Текст-подсказка

Начиная с 2017 года, Android включает в себя несколько методов взаимодействия с текстовой подсказкой объекта:

  • Методы isShowingHintText() и setShowingHintText() указывают и устанавливают, соответственно, соответствует ли текущее текстовое содержимое узла тексту подсказки этого узла.
  • getHintText() предоставляет доступ к самому тексту подсказки. Даже если объект не отображает текст подсказки, вызов getHintText() будет успешным.

Расположение текстовых символов на экране

На устройствах под управлением Android 8.0 (уровень API 26) и выше службы специальных возможностей могут определять экранные координаты ограничивающей рамки каждого видимого символа внутри виджета TextView . Службы находят эти координаты, вызывая метод refreshWithExtraData() , передавая в качестве первого аргумента EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY , а в качестве второго аргумента — объект Bundle . В процессе выполнения метода система заполняет аргумент Bundle массивом объектов Rect , допускающих разделение по папкам. Каждый объект Rect представляет собой ограничивающую рамку определенного символа.

Стандартизированные односторонние значения диапазона

Некоторые объекты AccessibilityNodeInfo используют экземпляр AccessibilityNodeInfo.RangeInfo для указания того, что элемент пользовательского интерфейса может принимать диапазон значений. При создании диапазона с помощью RangeInfo.obtain() или при получении крайних значений диапазона с помощью getMin() и getMax() следует помнить, что устройства под управлением Android 8.0 (уровень API 26) и выше представляют односторонние диапазоны стандартизированным образом:

  • Для диапазонов без минимального значения минимальное значение обозначается как Float.NEGATIVE_INFINITY .
  • Для диапазонов без максимального значения Float.POSITIVE_INFINITY представляет максимальное значение.

Реагировать на события, связанные с доступностью

Теперь, когда ваш сервис настроен на работу и прослушивание событий, напишите код, чтобы он знал, что делать при поступлении события AccessibilityEvent . Начните с переопределения метода onAccessibilityEvent(AccessibilityEvent) . В этом методе используйте getEventType() для определения типа события и getContentDescription() для извлечения любого текста метки, связанного с представлением, которое инициировало событие:

Котлин

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

Дополнительные ресурсы

Для получения более подробной информации ознакомьтесь со следующими ресурсами:

Гиды

Кодлабс