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

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

Создайте свою службу специальных возможностей

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

package com.example.android.apis.accessibility;

import android.accessibilityservice.AccessibilityService;

public class MyAccessibilityService extends AccessibilityService {
...
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    @Override
    public void onInterrupt() {
    }

...
}

Как и любую другую службу, вы также объявляете её в файле манифеста. Не забудьте указать, что он обрабатывает намерение android.accessibilityservice , таким образом сервис вызывается, когда приложения посылает AccessibilityEvent.

<application ...>
...
<service android:name=".MyAccessibilityService">
     <intent-filter>
         <action android:name="android.accessibilityservice.AccessibilityService" />
     </intent-filter>
     . . .
</service>
...
</application>

Если вы создали новый проект для службы, и не планируете иметь приложение, вы можете удалить Activity класс (обычно называемый MainActivity.java) из вашего проекта. Не забудьте также удалить соответствующий элемент деятельности из вашего манифеста.

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

Установка переменных конфигурации вашей службы специальных возможностей сообщает системе, как и когда вы хотите её выполнять. На какие типы событий вы бы хотели отвечать? Должна ли служба быть активна для всех приложений, или только для конкретных имён пакетов ? Какой отличительный тип обратной связи будет использоваться?

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

@Override
public void onServiceConnected() {
    // Set the type of events that this service wants to listen to.  Others
    // won't be passed to this service.
    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
            AccessibilityEvent.TYPE_VIEW_FOCUSED;

    // If you only want this service to work with specific applications, set their
    // package names here.  Otherwise, when the service is activated, it will listen
    // to events from all applications.
    info.packageNames = new String[]
            {"com.example.android.myFirstApp", "com.example.android.mySecondApp"};

    // Set the type of feedback your service will provide.
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;

    // Default services are invoked only if no package-specific ones are present
    // for the type of AccessibilityEvent generated.  This service *is*
    // application-specific, so the flag isn't necessary.  If this was a
    // general-purpose service, it would be worth considering setting the
    // DEFAULT flag.

    // info.flags = AccessibilityServiceInfo.DEFAULT;

    info.notificationTimeout = 100;

    this.setServiceInfo(info);

}

Начиная с Android 4.0, доступен второй вариант: настроить службу используя 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>

Реагирование на AccessibilityEvents

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

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    final int eventType = event.getEventType();
    String eventText = null;
    switch(eventType) {
        case AccessibilityEvent.TYPE_VIEW_CLICKED:
            eventText = "Focused: ";
            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);
    ...
}

Запрос иерархии представления для большего контекста

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

android:canRetrieveWindowContent="true"

Как только это будет сделано, получите AccessibilityNodeInfo объект с помощью getSource(). Этот вызов только возвращает объект, если окно, где возникло событие по-прежнему активно. Если нет, то он вернет null, поэтому действуйте соответственно. Следующий пример это фрагмент кода, который, когда он получает событие, делает следующее:

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

// 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 fired 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 or not it's complete, based on
    // the text inside the label, and the state of the check-box.
    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);
}

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