Criar seu próprio serviço de acessibilidade

Um serviço de acessibilidade é um app que melhora a interface do usuário para ajudar usuários com deficiência ou que possam estar temporariamente incapazes de interagir plenamente com um dispositivo. Por exemplo, usuários que estão dirigindo, cuidando de uma criança pequena, ou participar de uma festa com barulho pode precisar de uma interface feedback.

O Android oferece serviços de acessibilidade padrão, incluindo: TalkBack e os desenvolvedores podem criar e distribuir os próprios serviços. Este documento explica os fundamentos da criação de um serviço de acessibilidade.

Um serviço de acessibilidade pode ser empacotado em um aplicativo normal ou criado como um um projeto Android independente. As etapas para criar o serviço são as mesmas no em qualquer situação.

Criar um serviço de acessibilidade

Em seu projeto, crie uma classe que estende 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() {
    }

...
}

Se você criar um novo projeto para este Service e não planeja ter um app associada, remova a classe inicial Activity do seu fonte.

Declarações e permissões do manifesto

Os apps que oferecem serviços de acessibilidade precisam incluir declarações específicas na os manifestos do app sejam tratados como um serviço de acessibilidade pelo Android sistema. Esta seção explica as configurações obrigatórias e opcionais de serviços de acessibilidade.

Declaração de serviço de acessibilidade

Para que o app seja tratado como um serviço de acessibilidade, inclua um service em vez do elemento activity, no elemento application no manifesto. Além disso, dentro do elemento service, inclua um filtro de intent do serviço de acessibilidade. O manifesto também precisa proteger o serviço adicionando o BIND_ACCESSIBILITY_SERVICE para garantir que somente o sistema possa se vincular a ela. Veja um exemplo:

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

Configuração do serviço de acessibilidade

Os serviços de acessibilidade precisam fornecer uma configuração que especifique os tipos de eventos de acessibilidade que o serviço gerencia e informações adicionais sobre o serviço. A configuração de um serviço de acessibilidade está contida no AccessibilityServiceInfo . Seu serviço pode criar e definir uma configuração usando uma instância do classe e setServiceInfo() durante a execução. No entanto, nem todas as opções de configuração estão disponíveis usando essa .

É possível incluir um elemento <meta-data> no manifesto com uma referência a um de configuração do Terraform, que permite definir todas as opções serviço de acessibilidade, conforme mostrado no exemplo a seguir:

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

Esse elemento <meta-data> se refere a um arquivo XML que você cria no seu Diretório de recursos do app: <project_dir>/res/xml/accessibility_service_config.xml>. O código a seguir mostra um exemplo do conteúdo do arquivo de configuração de serviço:

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

Para mais informações sobre os atributos XML que podem ser usados no de configuração do serviço de acessibilidade, consulte a referência a seguir documentação:

Para mais informações sobre quais configurações podem ser definidas dinamicamente durante a execução, consulte AccessibilityServiceInfo documentação de referência.

Configurar o serviço de acessibilidade

Considere o seguinte ao definir as variáveis de configuração da sua serviço de acessibilidade para informar ao sistema como e quando executar:

  • A quais tipos de evento você quer que ela responda?
  • O serviço precisa estar ativo para todos os aplicativos ou apenas para um pacote específico nomes?
  • Que tipos diferentes de feedback ele usa?

Você tem duas opções para definir essas variáveis. A opção compatível com versões anteriores é defini-los em código, usando setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) Para fazer isso, modifique o atributo onServiceConnected() e configure seu serviço, conforme mostrado no exemplo a seguir:

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

}

A segunda opção é configurar o serviço usando um arquivo XML. Certo opções de configuração, como canRetrieveWindowContent, estarão disponíveis apenas se você configurar seu serviço usando XML. A configuração as opções do exemplo anterior ficam assim quando definidas usando 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"
/>

Se você usa XML, referencie-o no manifesto adicionando um tag <meta-data> à sua declaração de serviço apontando para o arquivo XML. Se você armazenar o arquivo XML res/xml/serviceconfig.xml, a nova tag terá esta aparência:

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

Métodos do serviço de acessibilidade

Um serviço de acessibilidade precisa estender a classe AccessibilityService e substitua os seguintes métodos dessa classe. Esses métodos são apresentados a ordem que o sistema Android os chama: a partir do início do serviço (onServiceConnected()) enquanto estiver em execução (onAccessibilityEvent(), onInterrupt()). até quando ele for desligado (onUnbind()).

  • onServiceConnected(): (opcional) o sistema chama esse método quando conecta ao seu serviço de acessibilidade. Use este método para fazer a configuração única etapas para seu serviço, incluindo conectar-se ao sistema de feedback do usuário serviços, como o gerenciador de áudio ou a vibração do dispositivo. Se você quiser definir a configuração do serviço no ambiente de execução ou fazer ajustes únicos, esse é um local conveniente para chamar setServiceInfo().

  • onAccessibilityEvent() (obrigatório): o sistema chama esse método quando ele detecta AccessibilityEvent que corresponda aos parâmetros de filtragem de eventos especificados pela sua serviço, como quando o usuário toca em um botão ou foca em uma interface do usuário em um app sobre o qual o serviço de acessibilidade está fornecendo feedback. Quando o sistema chamar esse método, ele transmitirá o AccessibilityEvent associado, que o serviço pode interpretar e usar para fornecer feedback ao usuário. Esse método pode ser chamado várias vezes durante o ciclo de vida do serviço.

  • onInterrupt() (obrigatório): o sistema chama esse método quando o sistema quiser interromper o feedback que seu serviço está fornecendo, geralmente em resposta a uma ação do usuário, como mover o foco para um controle diferente. Isso pode ser chamado várias vezes durante o ciclo de vida do serviço.

  • onUnbind(): (opcional) o sistema chama esse método quando o sistema é prestes a encerrar o serviço de acessibilidade. Use esse método para fazer procedimentos de desligamento único, incluindo a desalocação do sistema de feedback do usuário serviços, como o gerenciador de áudio ou a vibração do dispositivo.

Esses métodos de callback fornecem a estrutura básica para a acessibilidade serviço. Você pode decidir como processar os dados fornecidos pelo sistema Android em a forma de objetos AccessibilityEvent e fornecem feedback ao usuário. Para mais informações sobre como acessar informações de um evento de acessibilidade, consulte Receber detalhes do evento.

Registrar-se nos eventos de acessibilidade

Uma das funções mais importantes da configuração do serviço de acessibilidade é permitir que você especifique quais tipos de eventos de acessibilidade seu serviço oferece. A especificação dessas informações permite que os serviços de acessibilidade colaborem uns com os outros e oferece flexibilidade para lidar apenas com eventos tipos de apps específicos. A filtragem de eventos pode incluir o seguinte: critérios:

  • Nomes de pacote:especificam os nomes dos pacotes de apps com acessibilidade eventos que você quer que o serviço processe. Se esse parâmetro for omitido, o serviço de acessibilidade é considerado disponível para a acessibilidade do serviço eventos de qualquer aplicativo. É possível definir esse parâmetro no serviço de acessibilidade de configuração com o atributo android:packageNames como um lista separada por vírgulas ou use o AccessibilityServiceInfo.packageNames participante.

  • Tipos de evento:especifique os tipos de eventos de acessibilidade que você quer que um serviço gerenciado. É possível definir esse parâmetro no serviço de acessibilidade de configuração com o atributo android:accessibilityEventTypes como uma lista separada pelo caractere |, por exemplo, accessibilityEventTypes="typeViewClicked|typeViewFocused" Ou você pode definir usando o AccessibilityServiceInfo.eventTypes participante.

Ao configurar o serviço de acessibilidade, considere cuidadosamente quais eventos pode processar e se registrar apenas para esses eventos. Como os usuários podem ativar mais de um serviço de acessibilidade por vez, seu serviço não pode consumir que ele não suporta. Lembre-se de que outros serviços podem lidar com essas para melhorar a experiência do usuário.

Volume da acessibilidade

Dispositivos com o Android 8.0 (nível 26 da API) e versões mais recentes incluem o STREAM_ACCESSIBILITY na categoria de volume, que permite controlar o volume dos recursos de acessibilidade saída de áudio do serviço de forma independente dos outros sons do dispositivo.

Os serviços de acessibilidade podem usar esse tipo de stream configurando o FLAG_ENABLE_ACCESSIBILITY_VOLUME é a melhor opção. Depois, você pode mudar o volume do áudio de acessibilidade do dispositivo chamando as adjustStreamVolume() na instância do dispositivo de AudioManager

O snippet de código a seguir demonstra como um serviço de acessibilidade pode usar a Categoria de volume 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);
        }
    }
}

Para mais informações, assista ao vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 18h35.

Atalho de acessibilidade

Em dispositivos com o Android 8.0 (nível 26 da API) e versões mais recentes, os usuários podem ativar desativar o serviço de acessibilidade de sua preferência em qualquer tela pressionando e segurando as duas teclas de volume ao mesmo tempo. Embora esse atalho ative e desativa o Talkback por padrão, os usuários podem configurar o botão para ativar e desativar qualquer serviço instalado no dispositivo.

Para que os usuários acessem um serviço de acessibilidade específico pela o serviço precisa solicitar o recurso durante a execução.

Para mais informações, assista ao vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 13h25.

Botão de acessibilidade

Em dispositivos que usam uma área de navegação renderizada por software e executam o Android 8.0 (API de nível 26) ou superior, a lateral direita da barra de navegação inclui uma botão de acessibilidade. Quando os usuários pressionam esse botão, eles podem invocar uma das vários recursos e serviços de acessibilidade ativados, dependendo do conteúdo exibido na tela no momento.

Para permitir que os usuários invoquem determinado serviço de acessibilidade usando o o serviço precisa adicionar o FLAG_REQUEST_ACCESSIBILITY_BUTTON na android:accessibilityFlags de um objeto AccessibilityServiceInfo. . O serviço pode então registrar callbacks usando registerAccessibilityButtonCallback()

O snippet de código a seguir demonstra como configurar uma solicitação serviço para responder quando o usuário pressionar o botão de acessibilidade:

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

Para mais informações, assista ao vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 16h28.

Gestos de impressão digital

Serviços de acessibilidade em dispositivos com o Android 8.0 (nível 26 da API) ou versões mais recentes pode responder a gestos de deslizar direcionais (para cima, para baixo, para a esquerda e para a direita) ao longo do sensor de impressão digital Para configurar um serviço para receber callbacks sobre esses interações, complete a seguinte sequência:

  1. Declarar o USE_BIOMETRIC e a permissão CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES capacidade de processamento.
  2. Defina o FLAG_REQUEST_FINGERPRINT_GESTURES. no atributo android:accessibilityFlags.
  3. Registre-se para callbacks usando registerFingerprintGestureCallback().
.

Lembre-se de que nem todos os dispositivos têm sensores de impressão digital. Para identificar se um dispositivo é compatível com o sensor, use o isHardwareDetected() . Mesmo em um dispositivo com sensor de impressão digital, o serviço não pode use o sensor quando ele estiver em uso para fins de autenticação. Para identificar quando se o sensor estiver disponível, chame o método isGestureDetectionAvailable() e implementar o método onGestureDetectionAvailabilityChanged() o retorno de chamada.

O snippet de código a seguir mostra um exemplo de como usar gestos de impressão digital para navegar em um tabuleiro de jogo virtual:

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

Para mais informações, assista ao vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 9h03

Conversão de texto em voz multilíngue

A partir do Android 8.0 (nível 26 da API), o serviço de conversão de texto em voz (TTS) do Android consegue identificar e falar frases em vários idiomas dentro de um único bloco de em textos. Para ativar esse recurso de troca automática de idioma em um ambiente serviço, junte todas as strings Objetos LocaleSpan, conforme mostrado no seguinte snippet de código:

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

Para mais informações, assista ao vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 10h59

Agir em nome dos usuários

Desde 2011, os serviços de acessibilidade podem agir em nome dos usuários, incluindo: mudar o foco da entrada e selecionar (ativar) elementos da interface do usuário. Em em 2012, a variedade de ações foi expandida para incluir listas de rolagem e interação com campos de texto. Os serviços de acessibilidade também podem realizar ações globais, como navegar até a tela inicial, pressionar o botão Voltar e abrir o tela de notificações e lista de apps recentes. Desde 2012, o Android inclui acessibilidade de foco, que torna todos os elementos visíveis selecionáveis por um serviço de acessibilidade.

Com esses recursos, os desenvolvedores de serviços de acessibilidade podem criar de navegação, como a navegação por gestos, e permitem que usuários com deficiências melhor controle de dispositivos com tecnologia Android.

Ouvir gestos

Os serviços de acessibilidade podem detectar gestos específicos e responder agindo de acordo em nome de um usuário. Esse recurso exige que a solicitação de serviço de acessibilidade ativação do recurso Explorar por toque. Seu serviço pode solicitar isso ativação definindo o flags membro da instância AccessibilityServiceInfo do serviço para FLAG_REQUEST_TOUCH_EXPLORATION_MODE, como mostrado no exemplo a seguir.

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

Depois que o serviço solicitar a ativação do Explorar por toque, o usuário precisará permitir o recurso ser ativado, se ainda não estiver ativo. Quando esse recurso é ativo, seu serviço recebe notificações de gestos de acessibilidade por meio de do seu serviço onGesture() e pode responder agindo em nome do usuário.

Gestos contínuos

Dispositivos com o Android 8.0 (nível 26 da API) oferecem suporte a gestos contínuos ou gestos programáticos que contêm mais de um objeto Path.

Ao especificar uma sequência de traços, é possível especificar que eles pertencem ao o mesmo gesto programático usando o argumento final willContinue na GestureDescription.StrokeDescription , conforme mostrado no seguinte snippet de código:

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

Para mais informações, assista ao vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 15h47.

Usar ações de acessibilidade

Os serviços de acessibilidade podem atuar em nome dos usuários para simplificar as interações com e aumentar a produtividade. A capacidade dos serviços de acessibilidade e como realizar ações foi adicionado em 2011 e ampliado significativamente em 2012.

Para agir em nome dos usuários, seu serviço de acessibilidade precisa ser registrado para receber eventos de apps e solicitar permissão para visualizar o conteúdo dos aplicativos definindo android:canRetrieveWindowContent como true no arquivo de configuração de serviço. Quando os eventos são recebidos pela serviço, ele pode recuperar o AccessibilityNodeInfo do evento usando getSource() Com o objeto AccessibilityNodeInfo, o serviço pode explorar a visualização para determinar qual ação realizar e agir em nome do usuário usando 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();
    }
    ...
}

O método performAction() permite que o serviço realize uma ação app. Se o serviço precisar executar uma ação global, como navegar até a tela inicial, tocar no botão Voltar ou abrir o tela de notificações ou lista de aplicativos recentes e, em seguida, use o performGlobalAction() .

Usar tipos de foco

Em 2012, o Android lançou um foco da interface do usuário chamado foco de acessibilidade. Os serviços de acessibilidade podem usar esse foco para selecionar qualquer interface do usuário visível elemento e agir com base nele. Esse tipo de foco é diferente do foco de entrada, que determina qual elemento da interface do usuário na tela recebe entrada quando um usuário digita caracteres, pressiona Enter no teclado ou empurra o centro botão direcional de um botão direcional.

É possível que um elemento em uma interface do usuário tenha foco de entrada enquanto outro elemento tem foco na acessibilidade. O objetivo do foco da acessibilidade é para fornecer aos serviços de acessibilidade um método de interação com elementos em uma tela, independentemente de o elemento ser focalizável na entrada uma perspectiva de sistema. Para garantir que seu serviço de acessibilidade interaja corretamente com os elementos de entrada, siga as orientações para testar os acessibilidade para testar seu serviço ao usar um aplicativo típico.

Um serviço de acessibilidade pode determinar qual elemento da interface do usuário tem entrada ou foco da acessibilidade usando o AccessibilityNodeInfo.findFocus() . Também é possível pesquisar elementos que possam ser selecionados com o foco de entrada usando o focusSearch() . Por fim, o serviço de acessibilidade pode definir o foco da acessibilidade usando as performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS) .

Coletar informações

Os serviços de acessibilidade têm métodos padrão para coletar e representar unidades de informações fornecidas pelo usuário, como detalhes do evento, texto e números.

Ver detalhes de mudanças de janelas

O Android 9 (nível 28 da API) e versões mais recentes permitem que os apps acompanhem as atualizações da janela um app redesenha várias janelas simultaneamente. Quando um TYPE_WINDOWS_CHANGED evento ocorrer, use o método getWindowChanges() para determinar como as janelas mudam. Durante uma atualização de várias janelas, cada produz o próprio conjunto de eventos. O método getSource() retorna a raiz da janela associada a cada evento.

Se um app define o painel de acessibilidade títulos para os View, o serviço pode reconhecer quando a interface do app é atualizada. Quando um TYPE_WINDOW_STATE_CHANGED evento ocorrer, use os tipos retornados pelo getContentChangeTypes() para determinar como a janela muda. Por exemplo, o framework pode detectar quando quando um painel tem um novo título ou desaparece.

Ver detalhes de evento

O Android fornece informações para serviços de acessibilidade sobre a interface do usuário usando objetos AccessibilityEvent. Nas versões anteriores do Android, as informações disponíveis em um evento de acessibilidade, além de fornecer detalhes sobre o controle da interface selecionado pelos usuários, oferecidos de forma limitada informações contextuais. Em muitos casos, essas informações de contexto ausentes podem ser fundamental para entender o significado do controle selecionado.

Um exemplo de interface em que o contexto é fundamental é um calendário ou dia. de dados. Se o usuário selecionar o horário 16h em uma lista de segunda a sexta-feira e o serviço de acessibilidade anuncia "16h", mas não informa o dia da semana. nome, dia do mês ou nome do mês, o feedback resultante será confusa. Nesse caso, o contexto de um controle de interface do usuário é fundamental para para um usuário que queira marcar uma reunião.

Desde 2011, o Android estende significativamente a quantidade de informações que uma serviço de acessibilidade pode obter sobre uma interação da interface do usuário escrevendo eventos de acessibilidade com base na hierarquia de visualização. Uma hierarquia de visualização é o conjunto componentes da interface do usuário que contêm o componente (seus pais) e o nome elementos de interface que podem estar contidos por esse componente (os filhos dele). Em dessa forma, o Android pode fornecer mais detalhes sobre eventos de acessibilidade, permitindo serviços de acessibilidade oferecem feedback mais útil aos usuários.

Um serviço de acessibilidade consegue informações sobre um evento da interface do usuário um AccessibilityEvent transmitido pelo sistema para a onAccessibilityEvent(). Esse objeto apresenta detalhes sobre o , incluindo o tipo de objeto usado, o texto descritivo e outros detalhes.

  • AccessibilityEvent.getRecordCount() e getRecord(int): esses métodos permitem que você recupere o conjunto de AccessibilityRecord objetos que contribuem para o AccessibilityEvent transmitido a você pelo sistema. Esse nível de detalhamento dá mais contexto para o evento que aciona seu serviço de acessibilidade.

  • AccessibilityRecord.getSource(): Esse método retorna um objeto AccessibilityNodeInfo. Esse objeto permite que você solicitar a hierarquia de layout de visualização (pais e filhos) do componente que origina o evento de acessibilidade. Com esse recurso, serviço investigam o contexto completo de um evento, incluindo o conteúdo e o estado de todas as visualizações filhas ou incluídas.

.

A plataforma Android oferece a capacidade de um AccessibilityService consultar da hierarquia de visualização, coletando informações sobre o componente de IU que gera um evento, bem como o pai e os filhos dele. Para fazer isso, defina a seguinte linha na sua configuração XML:

android:canRetrieveWindowContent="true"

Em seguida, receba um objeto AccessibilityNodeInfo usando getSource(). Essa chamada só retorna um objeto se a janela de origem do evento for a janela ativa. Caso contrário, ele retorna nulo. Portanto, comporte-se de acordo.

No exemplo abaixo, o código faz o seguinte quando um evento é recebido:

  1. Captura imediatamente o pai da visualização em que o evento se origina.
  2. Nessa visualização, procura um rótulo e uma caixa de seleção como visualizações filhas.
  3. Se os encontrar, cria uma string para informar ao usuário, indicando o e se ele foi verificado.

Se, em algum momento, um valor nulo for retornado ao atravessar a hierarquia de visualização, o método desiste silenciosamente.

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

Agora, você tem um serviço de acessibilidade completo e funcional. Tente configurar como ele interage com o usuário adicionando a conversão de texto em voz do Android Engine ou usar Vibrator para ativar o retorno tátil feedback.

Processar texto

Os dispositivos com o Android 8.0 (API de nível 26) ou versões mais recentes incluem vários recursos de processamento de texto que facilitam a identificação e a operação dos serviços de acessibilidade em unidades de texto específicas exibidas na tela.

Dicas

O Android 9 (API de nível 28) introduz vários recursos que dão acesso a dicas na interface de um app. Usar getTooltipText() para ler o texto de uma dica e usar o ACTION_SHOW_TOOLTIP e ACTION_HIDE_TOOLTIP para instruir instâncias de View a mostrar ou ocultar dicas.

Texto de dica

Desde 2017, o Android inclui vários métodos para interagir com uma texto de dica do objeto baseado em texto:

  • A isShowingHintText() e setShowingHintText() indicam e definem, respectivamente, se o texto atual do nó content representa o texto de dica do nó.
  • getHintText() fornece acesso ao próprio texto de dica. Mesmo se um objeto não estiver sendo exibido Texto de dica, chamar getHintText() funciona.

Locais de caracteres de texto na tela

Em dispositivos com o Android 8.0 (nível 26 da API) e versões mais recentes, os serviços de acessibilidade pode determinar as coordenadas da tela para a caixa delimitadora de cada caractere visível. em um widget TextView. Serviços encontrar essas coordenadas chamando refreshWithExtraData(), passando EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY como primeiro argumento e um objeto Bundle como segundo argumento. À medida que o método é executado, o sistema preenche a O argumento Bundle com uma matriz parcelable de objetos Rect. Cada objeto Rect representa a caixa delimitadora de um caractere específico.

Valores padronizados de intervalo unilateral

Alguns objetos AccessibilityNodeInfo usam uma instância do AccessibilityNodeInfo.RangeInfo para indicar que um elemento da interface pode assumir um intervalo de valores. Ao criar um intervalo usando RangeInfo.obtain(), ou ao recuperar os valores extremos do intervalo getMin() e getMax(), dispositivos com o Android 8.0 (nível 26 da API) e versões mais recentes representam intervalos unilaterais de forma padronizada:

Responder a eventos de acessibilidade

Agora que o serviço está configurado para executar e detectar eventos, escreva o código para que ele saiba o que fazer quando um AccessibilityEvent chegar. Comece substituindo a string onAccessibilityEvent(AccessibilityEvent) . Nesse método, use getEventType() para determinar o tipo de evento e getContentDescription() para extrair qualquer texto de rótulo associado à visualização que dispara o evento:

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

Outros recursos

Para saber mais, consulte os recursos a seguir:

Guias

Codelabs