Um serviço de acessibilidade é um aplicativo que oferece melhorias na interface do usuário para ajudar aqueles que têm alguma incapacidade ou que não estão podendo interagir de forma completa com o dispositivo. Por exemplo, usuários que estão dirigindo, cuidando de uma criança pequena ou participando de uma festa muito barulhenta podem precisar de feedback extra ou alternativo da interface.
O Android oferece serviços de acessibilidade padrão, incluindo o "TalkBack", e os desenvolvedores podem criar e distribuir os próprios serviços. Este documento explica os princípios básicos da criação de um serviço de acessibilidade.
Observação: seu app precisa usar serviços de acessibilidade no nível da plataforma apenas para ajudar usuários com deficiências a interagir com o app.
A capacidade de criar e implantar serviços de acessibilidade foi introduzida com o Android 1.6 (API de nível 4) e recebeu melhorias significativas com o Android 4.0 (API de nível 14). A Biblioteca de Suporte do Android também foi atualizada com o lançamento do Android 4.0 para oferecer compatibilidade com esses recursos avançados de acessibilidade para o Android 1.6. Os desenvolvedores que buscam serviços amplamente compatíveis precisam usar a Biblioteca de Suporte e desenvolver para os recursos de acessibilidade mais avançados introduzidos no Android 4.0.
Criar um serviço de acessibilidade
O serviço de acessibilidade pode ser empacotado em um aplicativo normal ou criado
como um projeto Android independente. As etapas para a criação do serviço são as mesmas
em qualquer situação. No seu projeto, crie uma classe que estenda 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ê criou um novo projeto para esse serviço e não pretende ter um app associado a ele, remova a classe de atividade inicial da sua origem.
Declarações e permissões do manifesto
Os aplicativos que oferecem serviços de acessibilidade precisam incluir declarações específicas nos manifestos para serem tratados como esse tipo de serviço pelo sistema Android. Esta seção explica as configurações obrigatórias e opcionais dos serviços de acessibilidade.
Declaração de serviço de acessibilidade
Para ser tratado como serviço de acessibilidade, é preciso incluir um
elemento service
(em vez do activity
) no elemento application
do manifesto. Além disso, também é necessário incluir um
filtro de intent do serviço no elemento service
. Para compatibilidade com o Android 4.1 e versões mais recentes, o manifesto
também precisa proteger o serviço adicionando a permissão BIND_ACCESSIBILITY_SERVICE
para garantir que somente o sistema possa se vincular a ele. 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>
Essas declarações são obrigatórias para todos os serviços de acessibilidade implantados no Android 1.6 (API de nível 4) ou versões mais recentes.
Configuração do serviço de acessibilidade
Os serviços de acessibilidade também precisam oferecer uma configuração que especifique os tipos de
eventos de acessibilidade que eles processam e outras informações sobre o serviço. A
configuração de um serviço desse tipo está contida na classe AccessibilityServiceInfo
. O serviço pode criar e definir uma
configuração usando uma instância dessa classe e setServiceInfo()
durante a execução.
No entanto, nem todas as opções de configuração estão disponíveis com esse método.
A partir do Android 4.0, é possível incluir um elemento <meta-data>
no manifesto
com uma referência a um arquivo de configuração, que permite definir uma gama completa de opções para
o serviço de acessibilidade, como mostrado no exemplo a seguir:
<service android:name=".MyAccessibilityService"> ... <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>
Esse elemento de metadados se refere a um arquivo XML que você cria no diretório de
recursos do seu aplicativo (<project_dir>/res/xml/accessibility_service_config.xml
). O código a seguir
mostra um exemplo de conteúdo para o arquivo de configuração do 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 arquivo de configuração do serviço de acessibilidade, siga estes links para a documentação de referência:
android:description
android:packageNames
android:accessibilityEventTypes
android:accessibilityFlags
android:accessibilityFeedbackType
android:notificationTimeout
android:canRetrieveWindowContent
android:settingsActivity
Para mais informações sobre quais configurações podem ser definidas dinamicamente durante a execução, consulte
a documentação de referência da classe AccessibilityServiceInfo
.
Configurar o serviço de acessibilidade
A definição das variáveis de configuração para o serviço de acessibilidade informa ao sistema como e quando você quer executá-lo. A quais tipos de evento você gostaria de responder? O serviço precisa estar ativo para todos os aplicativos ou apenas para nomes de pacotes específicos? 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 é configurá-las no código usando setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)
.
Para fazer isso, modifique o método onServiceConnected()
e configure o serviço nele.
Kotlin
override fun onServiceConnected() { info.apply { // Set the type of events that this service wants to listen to. Others // won't be passed to this service. eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or 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. packageNames = arrayOf("com.example.android.myFirstApp", "com.example.android.mySecondApp") // Set the type of feedback your service will provide. 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. // 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 // 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); }
A segunda opção é configurar o
serviço usando um arquivo XML. Algumas opções de configuração, como
canRetrieveWindowContent
, só estarão disponíveis se você
configurar o serviço usando XML. As mesmas opções de configuração acima, definidas
com XML, seriam assim:
<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ê optar pelo XML, referencie-o no manifesto adicionando
uma tag <meta-data> à
declaração de serviço, apontando para o arquivo XML. Se você armazenou o arquivo XML
em res/xml/serviceconfig.xml
, a nova tag ficará assim:
<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 modificar os seguintes
métodos dela. Esses métodos são apresentados na ordem em que são chamados pelo sistema Android,
começando pelo momento em que o serviço é iniciado
(onServiceConnected()
),
passando pela execução (onAccessibilityEvent()
,
onInterrupt()
) e terminando no momento em que ele é
encerrado (onUnbind()
).
onServiceConnected()
(opcional): o sistema chama esse método quando se conecta ao serviço de acessibilidade. Use-o para seguir qualquer etapa única de configuração do serviço, incluindo conexão com os serviços do sistema de feedback do usuário, como o gerenciador de áudio ou a vibração do dispositivo. Caso você queira definir a configuração do serviço durante a execução ou fazer mudanças únicas, esse é um local conveniente para chamarsetServiceInfo()
.onAccessibilityEvent()
(obrigatório): esse método recebe um callback do sistema quando detecta umAccessibilityEvent
correspondente aos parâmetros de filtragem de eventos especificados pelo serviço de acessibilidade. Por exemplo, quando o usuário clica em um botão ou focaliza um controle da interface do usuário em um aplicativo a que o serviço de acessibilidade está oferecendo feedback. Quando isso acontece, o sistema chama esse método e transmite oAccessibilityEvent
associado, que o serviço pode interpretar e usar para oferecer feedback ao usuário. Esse método pode ser chamado várias vezes durante o ciclo de vida do serviço.onInterrupt()
(obrigatório): esse método é chamado quando o sistema quer interromper o feedback oferecido pelo serviço, geralmente em resposta a uma ação do usuário, como mover o foco para um controle diferente. Esse método pode ser chamado várias vezes durante o ciclo de vida do serviço.onUnbind()
: (opcional): esse método é chamado quando o sistema está prestes a encerrar o serviço de acessibilidade. Use esse método para seguir qualquer procedimento único de encerramento, incluindo desalocação dos serviços do sistema de feedback do usuário, como o gerenciador de áudio ou a vibração do dispositivo.
Esses métodos de callback oferecem a estrutura básica para o serviço de acessibilidade. Cabe a você
decidir como processar os dados fornecidos pelo sistema Android na forma de objetos AccessibilityEvent
e oferecer feedback ao usuário. Para saber
mais sobre como acessar informações de um evento de acessibilidade, consulte
Ver detalhes de evento.
Registrar-se nos eventos de acessibilidade
Uma das funções mais importantes dos parâmetros de configuração do serviço de acessibilidade é permitir que você especifique os tipos de eventos de acessibilidade que o serviço pode processar. A capacidade de especificar essas informações permite que os serviços de acessibilidade colaborem entre si e que você, como desenvolvedor, tenha flexibilidade para lidar apenas com tipos específicos de eventos de determinados aplicativos. A filtragem de eventos pode incluir os seguintes critérios:
- Nomes de pacotes: especificam os nomes dos pacotes dos aplicativos cujos eventos de
acessibilidade você quer que o serviço processe. Se esse parâmetro for omitido, o serviço de acessibilidade será
considerado disponível para eventos de acessibilidade de serviço de qualquer aplicativo. Esse parâmetro pode ser definido
nos arquivos de configuração do serviço de acessibilidade com o
atributo
android:packageNames
na forma de lista separada por vírgulas ou por meio do membroAccessibilityServiceInfo.packageNames
. - Tipos de evento: especificam os tipos de eventos de acessibilidade a serem processados
pelo serviço. Esse parâmetro pode ser definido nos arquivos de configuração do serviço de acessibilidade com o
atributo
android:accessibilityEventTypes
na forma de lista separada pelo caractere|
(comoaccessibilityEventTypes="typeViewClicked|typeViewFocused"
) ou por meio do membroAccessibilityServiceInfo.eventTypes
.
Ao configurar o serviço de acessibilidade, considere cuidadosamente quais eventos ele pode processar e registre-se apenas para esses eventos. Como os usuários podem ativar mais de um serviço de acessibilidade por vez, é importante que o seu não consuma eventos que não seja capaz de processar. Lembre-se de que outros serviços podem processar esses eventos para melhorar a experiência do usuário.
Volume da acessibilidade
Dispositivos com o Android 8.0 (API de nível 26) ou versões mais recentes incluem
a categoria de volume STREAM_ACCESSIBILITY
, que permite controlar o volume da saída de
áudio do serviço de acessibilidade, independentemente de outros sons do dispositivo.
Os serviços de acessibilidade podem usar esse tipo de stream configurando a
opção
FLAG_ENABLE_ACCESSIBILITY_VOLUME
. É possível mudar o volume do áudio de acessibilidade do dispositivo chamando
o método
adjustStreamVolume()
na instância de AudioManager
do dispositivo.
O snippet de código a seguir mostra 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 saber mais, assista o vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 6:35.
Atalho de acessibilidade
Em dispositivos com o Android 8.0 (API de nível 26) ou versão mais recente, os usuários podem ativar e desativar o serviço de acessibilidade preferencial em qualquer tela tocando nas duas teclas de volume ao mesmo tempo e mantendo-as pressionadas. Embora esse atalho ative e desative o Talkback por padrão, o usuário pode configurar o botão para ativar e desativar qualquer serviço que esteja instalado no dispositivo, inclusive o seu.
Para que os usuários acessem um determinado serviço de acessibilidade a partir do atalho, o serviço precisa solicitar o recurso durante a execução, quando ele é iniciado.
Para saber mais, assista o vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 13:25.
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 versão mais recente, o lado direito da barra de navegação inclui um botão de acessibilidade. Quando os usuários pressionam esse botão, eles podem invocar um dos 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 um determinado serviço usando o
botão de acessibilidade, o serviço precisa adicionar a sinalização
FLAG_REQUEST_ACCESSIBILITY_BUTTON
em um atributo
android:accessibilityFlags
do objeto AccessibilityServiceInfo
. O serviço pode
registrar callbacks por meio de
registerAccessibilityButtonCallback()
.
Observação: esse recurso está disponível somente nos dispositivos que
oferecem uma área de navegação renderizada por software. Os serviços precisam usar
isAccessibilityButtonAvailable()
sempre e implementar onAvailabilityChanged()
para responder a mudanças com base
na disponibilidade do botão
de acessibilidade. Dessa forma, os usuários sempre poderão acessar a funcionalidade
do serviço, mesmo que o botão não seja compatível ou se torne
indisponível.
O snippet de código a seguir demonstra como configurar um serviço de acessibilidade para responder ao pressionamento do 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 saber mais, assista o vídeo da sessão Novidades de acessibilidade do Android (em inglês) no Google I/O 2017, a partir de 16:28.
Gestos de impressão digital
Os serviços de acessibilidade nos dispositivos com o Android 8.0 (API de nível 26) ou versões mais recentes podem responder a um mecanismo de entrada alternativo, deslizes direcionais (para cima, para baixo, para a esquerda e para a direita) no sensor de impressão digital de um dispositivo. Para configurar um serviço para receber callbacks sobre essas interações, siga estas etapas:
- Declare a permissão
USE_FINGERPRINT
e o recursoCAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES
. - Defina a sinalização
FLAG_REQUEST_FINGERPRINT_GESTURES
no atributoandroid:accessibilityFlags
. - Registre-se para callbacks usando
registerFingerprintGestureCallback()
.
Observação: permita que os usuários desativem a compatibilidade de um serviço de acessibilidade com gestos de impressão digital. Embora vários serviços de acessibilidade possam ouvir gestos de impressão digital simultaneamente, isso faz com que esses serviços entrem em conflito uns com os outros.
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
método isHardwareDetected()
. Mesmo que o dispositivo tenha um sensor de
impressão digital, não será possível usá-lo quando ele estiver sendo usado para fins
de autenticação. Para identificar quando o sensor está disponível, chame o
método isGestureDetectionAvailable()
e implemente o
callback onGestureDetectionAvailabilityChanged()
.
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" ... />
MyFingerprintGestureService.java
Kotlin
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
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 saber mais, assista o vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 9:03.
Conversão de texto em voz multilíngue
A partir do Android 8.0 (API de nível 26), o serviço de conversão de texto em voz (TTS, na sigla em inglês) do Android pode
identificar e falar frases em vários idiomas em um único bloco de texto.
Para ativar esse recurso automático de troca de idioma em um serviço
de acessibilidade, una todas as strings em objetos LocaleSpan
, como
mostrado no snippet de código a seguir:
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 saber mais, assista o vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 10:59.
Entrar em ação para os usuários
A partir do Android 4.0 (API de nível 14), os serviços de acessibilidade podem agir em nome dos usuários, o que inclui mudar o foco de entrada e selecionar (ativar) elementos da interface. No Android 4.1 (API de nível 16), a variedade de ações foi ampliada para incluir listas de rolagem e interação com campos de texto. Os serviços de acessibilidade também podem fazer ações globais, como navegar para a tela inicial, pressionar o botão "Voltar", abrir a tela de notificações e a lista de aplicativos recentes. O Android 4.1 também inclui um novo tipo de foco, o Foco de acessibilidade. Com ele, todos os elementos visíveis podem ser selecionados por um serviço de acessibilidade.
Esses novos recursos permitem que os desenvolvedores de serviços de acessibilidade criem modos alternativos de navegação, como a navegação por gestos, e ofereçam um controle avançado dos dispositivos Android aos usuários com deficiência.
Ouvir gestos
Os serviços de acessibilidade podem ouvir gestos específicos e responder agindo em nome
de um usuário. Esse recurso foi adicionado no Android 4.1 (API de nível 16) e requer que o
serviço de acessibilidade solicite a ativação do recurso "Reconhecer por toque". o serviço pode
solicitar essa ativação configurando o
membro flags
da
instância AccessibilityServiceInfo
do
serviço como 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 "Reconhecer por toque", o usuário precisará permitir
que o recurso seja ativado, caso ainda não esteja ativo. Quando esse recurso está ativo, o serviço
recebe notificações de gestos de acessibilidade por meio do
método de callback onGesture()
e pode responder realizando ações para o usuário.
Gestos contínuos
Os dispositivos que executam o Android 8.0 (API de nível 26) são compatíveis com gestos
contínuos programáticos, que contêm mais de um
objeto Path
.
Ao especificar sequências de traços, é preciso especificar que elas pertencem ao
mesmo gesto programático usando o argumento final,
willContinue
, no
construtor GestureDescription.StrokeDescription
, como mostrado no snippet
de código a seguir:
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 saber mais, assista o vídeo da sessão Novidades de acessibilidade do Android (em inglês) do Google I/O 2017, a partir de 15:47.
Usar ações de acessibilidade
Os serviços de acessibilidade podem realizar ações em nome dos usuários para tornar a interação com os aplicativos mais simples e produtiva. A capacidade dos serviços de acessibilidade de realizar ações foi adicionada no Android 4.0 (API de nível 14) e significativamente ampliada com o Android 4.1 (API de nível 16).
Para realizar ações em nome dos usuários, o serviço de acessibilidade precisa
se registrar para receber eventos de alguns ou muitos aplicativos e solicitar
permissão para visualizar o conteúdo relacionado. Para isso, é preciso definir
android:canRetrieveWindowContent
como true
no
arquivo de configuração do serviço. Quando os eventos são recebidos pelo
serviço, ele pode recuperar o
objeto AccessibilityNodeInfo
do evento usando
getSource()
.
Com o objeto AccessibilityNodeInfo
, o serviço pode
analisar a hierarquia de visualização para determinar qual ação realizar e colocá-la em prática para o usuário por meio
de 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 // take action 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 // take action 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 ações dentro de um aplicativo. Se o serviço precisar realizar uma
ação global, como navegar para a tela inicial, pressionar o botão "Voltar", abrir a
tela de notificações ou a lista de aplicativos recentes, use o
método
performGlobalAction()
.
Usar tipos de foco
O Android 4.1 (API de nível 16) introduz um novo tipo de foco da interface do usuário denominado Foco de acessibilidade. Os serviços de acessibilidade podem usar esse tipo de foco para selecionar qualquer elemento visível da interface do usuário e realizar ações nele. Esse tipo de foco é diferente do Foco de entrada, que é mais conhecido e determina qual elemento da interface do usuário na tela recebe entradas quando o usuário digita caracteres, pressiona Enter em um teclado ou o botão central de um controle direcional.
O foco de acessibilidade é completamente separado e independente do foco de entrada. Na verdade, é possível que um elemento em uma interface do usuário tenha foco de entrada enquanto outro tem foco de acessibilidade. O objetivo do foco de acessibilidade é oferecer aos serviços um método de interação com qualquer elemento visível na tela, independentemente de o elemento estar ou não com foco de entrada do ponto de vista do sistema. Veja o foco da acessibilidade em ação testando os gestos de acessibilidade. Para mais informações sobre como testar esse recurso, consulte Testar a navegação por gestos.
Observação: os serviços que usam foco de acessibilidade são responsáveis por sincronizar o foco de entrada atual quando um elemento é compatível com esse tipo de foco. Os serviços que não sincronizam o foco de entrada com o foco de acessibilidade correm o risco de causar problemas nos aplicativos que esperam que o foco de entrada esteja em um local específico quando determinadas ações são realizadas.
Um serviço de acessibilidade pode determinar qual elemento da interface do usuário tem o foco de entrada ou
de acessibilidade usando o método AccessibilityNodeInfo.findFocus()
. Também é possível pesquisar elementos que podem ser selecionados
com o foco de entrada usando o
método focusSearch()
.
Por fim, o serviço pode definir o foco de acessibilidade por meio
do método performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS)
.
Coletar informações
Os serviços de acessibilidade também têm métodos padrão de coleta e representação das principais unidades de informações fornecidas pelo usuário, como detalhes de evento, texto e números.
Ver detalhes de evento
O sistema Android oferece informações para serviços de acessibilidade sobre a interação com a interface do usuário
por meio de objetos AccessibilityEvent
. Antes do Android
4.0, as informações disponíveis em um evento de acessibilidade, embora oferecessem uma quantidade significativa de
detalhes sobre o controle de interface do usuário selecionado, disponibilizavam informações contextuais
limitadas. Em muitos casos, essas informações de contexto ausentes podem ser fundamentais para a compreensão
do significado do controle selecionado.
Um exemplo de interface em que o contexto é essencial é um calendário ou agenda diária. Se o usuário selecionar o horário das 16h em uma lista de segunda a sexta-feira, e o serviço de acessibilidade anunciar "16h", mas não anunciar o dia da semana, o dia do mês ou o nome do mês, o feedback resultante será confuso. Nesse caso, o contexto de um controle de interface é fundamental para o usuário que quer agendar uma reunião.
O Android 4.0 ampliou significativamente a quantidade de informações que um serviço de acessibilidade pode acessar sobre uma interação da interface do usuário, compondo eventos de acessibilidade com base na hierarquia de visualização. Uma hierarquia de visualização é o conjunto de componentes da interface do usuário que contém o componente (os pais) e os elementos da interface do usuário que podem estar contidos por esse componente (os filhos). Dessa forma, o sistema Android pode oferecer muito mais detalhes sobre os eventos de acessibilidade, permitindo que os serviços ofereçam feedback mais útil aos usuários.
Um serviço recebe informações sobre um evento da interface do usuário por meio de um AccessibilityEvent
passado pelo sistema para o método de callback
onAccessibilityEvent()
do serviço. Esse objeto traz detalhes sobre o evento, inclusive o
tipo de objeto que está sendo usado, o texto descritivo e outros detalhes relacionados. A partir do Android 4.0
(e compatível com versões anteriores por meio do objeto AccessibilityEventCompat
na Biblioteca de Suporte), você
pode ver mais informações sobre o evento usando as seguintes chamadas:
AccessibilityEvent.getRecordCount()
egetRecord(int)
: esses métodos permitem que você recupere o conjunto de objetosAccessibilityRecord
que contribuíram para oAccessibilityEvent
transmitido a você pelo sistema. Esse nível de detalhamento fornece mais contexto para o evento que acionou o serviço de acessibilidade.AccessibilityEvent.getSource()
: esse método retorna um objetoAccessibilityNodeInfo
. Esse objeto permite que você solicite a hierarquia de layouts de visualização (pais e filhos) do componente que originou o evento de acessibilidade. Esse recurso permite que um serviço de acessibilidade investigue o contexto completo de um evento, incluindo o conteúdo e o estado de quaisquer visualizações mães ou filhas contidas.Importante: a capacidade de investigar a hierarquia da visualização a partir de um
AccessibilityEvent
pode expor informações particulares do usuário ao serviço de acessibilidade. Por esse motivo, o serviço precisa solicitar esse nível de acesso por meio do arquivo XML de configuração do serviço de acessibilidade. Para isso, inclua o atributocanRetrieveWindowContent
e defina-o comotrue
. Se essa configuração não for incluída no arquivo XML de configuração do serviço, as chamadas paragetSource()
falharão.Observação: no Android 4.1 (API de nível 16) ou versões mais recentes, o método
getSource()
, assim comoAccessibilityNodeInfo.getChild()
egetParent()
, retorna somente os objetos de visualização considerados importantes para acessibilidade (visualizações que desenham conteúdo ou respondem a ações do usuário). Se o serviço exigir todas as visualizações, ele poderá solicitá-las configurando o membroflags
da instânciaAccessibilityServiceInfo
do serviço comoFLAG_INCLUDE_NOT_IMPORTANT_VIEWS
.
Ver detalhes de mudanças de janelas
O Android 9 (API de nível 28) e versões mais recentes permitem que os apps controlem as atualizações de janela
quando um app redesenha várias janelas ao mesmo tempo. Quando um
evento TYPE_WINDOWS_CHANGED
ocorrer, use a API
getWindowChanges()
para determinar como as janelas
foram modificadas. Durante uma atualização de várias janelas, cada janela produz o próprio conjunto
de eventos. O método
getSource()
retorna a visualização raiz da janela
associada a cada evento.
Se um app tiver definido
títulos
do painel de acessibilidade para os
objetos View
, o serviço
poderá reconhecer quando a IU do app for atualizada. Quando um evento
TYPE_WINDOW_STATE_CHANGED
ocorrer, use os tipos
retornados por
getContentChangeTypes()
para determinar como a janela foi
modificada. Por exemplo, o framework pode detectar quando um painel tem um novo título
ou desaparece.
Coletar detalhes do nó de acessibilidade
Essa etapa é opcional, mas muito útil. A Plataforma Android permite que um
AccessibilityService
consulte a hierarquia
de visualização, coletando informações sobre o componente de IU que gerou um evento, bem como sobre
o pai e os filhos desse componente. Para fazer isso, defina a
seguinte linha na sua configuração XML:
android:canRetrieveWindowContent="true"
Em seguida, providencie um objeto AccessibilityNodeInfo
usando getSource()
. Essa chamada só
retornará um objeto se a janela em que o evento foi originado ainda for a janela
ativa. Caso contrário, ela retornará nulo. Portanto, escolha o comportamento adequado. O
exemplo a seguir é um snippet de código que, ao receber
um evento:
- captura imediatamente a visualização mãe em que o evento foi originado;
- nessa visualização, procura uma etiqueta e uma caixa de seleção como visualizações filhas;
- se elas forem encontradas, cria uma string para informar o usuário, indicando a etiqueta e se ela foi verificada ou não;
- se um valor nulo for retornado a qualquer momento durante a transferência da hierarquia de visualização, o método será cancelado 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 fired 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 or not it's complete, based on // the text inside the label, and the state of the check-box. 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 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); }
Agora, você tem um serviço de acessibilidade completo e funcional. Configure
o modo como ele interage com o usuário adicionando o mecanismo de conversão de texto em voz (link em inglês) ou usando um Vibrator
para oferecer retorno
tátil.
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 IU do app. Use
getTooltipText()
para ler o texto de uma dica e use
ACTION_SHOW_TOOLTIP
e
ACTION_HIDE_TOOLTIP
para instruir instâncias de
View
a mostrar ou ocultar
as dicas.
Texto de dica
O Android 8.0 (API de nível 26) inclui vários métodos para interagir com a dica de um objeto baseado em texto:
- Os métodos
isShowingHintText()
esetShowingHintText()
indicam e definem, respectivamente, se o conteúdo de texto atual do nó representa o texto de dica do nó. - Para acessar o texto de dica, use
getHintText()
. Mesmo que um objeto não esteja exibindo texto de dica no momento, uma chamada paragetHintText()
pode ser realizada.
Locais de caracteres de texto na tela
Nos dispositivos com o Android 8.0 (API de nível 26) ou versões mais recentes, os serviços
de acessibilidade podem determinar as coordenadas da tela para cada caixa delimitadora
de caracteres visíveis em um widget TextView
. Os serviços encontram
essas coordenadas chamando
refreshWithExtraData()
e passando
EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
como o primeiro argumento e um objeto Bundle
como o segundo
argumento. À medida que o método é executado, o sistema preenche 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 de AccessibilityNodeInfo.RangeInfo
para indicar que um elemento de IU pode assumir
um intervalo de valores. Ao criar um intervalo usando
RangeInfo.obtain()
ou ao recuperar os valores extremos do intervalo por meio de
getMin()
e de getMax()
, lembre-se de que os dispositivos com o Android 8.0 (API de nível 26) e
versões mais recentes representam intervalos unilaterais de forma padronizada:
- Para intervalos sem mínimo,
Float.NEGATIVE_INFINITY
representa o valor mínimo. - Para intervalos sem máximo,
Float.POSITIVE_INFINITY
representa o valor máximo.
Responder a eventos de acessibilidade
Agora que o serviço está configurado para executar e ouvir eventos, grave código
para que ele saiba o que fazer quando um AccessibilityEvent
realmente for recebido. Comece
modificando o método onAccessibilityEvent(AccessibilityEvent)
.
Nesse método, use getEventType()
para determinar o
tipo de evento e getContentDescription()
para extrair
qualquer texto de etiqueta associado à visualização que acionou 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 seguintes recursos: