Conceitos e implementação do Jetpack Compose
Um serviço de acessibilidade é um app que melhora a interface do usuário para ajudar pessoas com deficiência ou que podem estar temporariamente impossibilitadas de interagir com um 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.
Um serviço de acessibilidade pode ser empacotado em um app normal ou criado como um projeto do Android independente. As etapas para criar o serviço são as mesmas em qualquer situação.
Criar um serviço de acessibilidade
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ê criar um projeto para este Service e não planeja ter um app
associado a ele, remova a classe Activity inicial da sua
origem.
Declarações e permissões do manifesto
Os apps 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 que seu app seja tratado como um serviço de acessibilidade, inclua um elemento service
em vez do elemento activity no elemento application
do manifesto. Além disso, inclua um filtro de intent de serviço de acessibilidade no elemento service. O manifesto também precisa proteger o serviço
adicionando a permissão BIND_ACCESSIBILITY_SERVICE para garantir que apenas
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>
Configuração do serviço de acessibilidade
Os serviços de acessibilidade 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 de acessibilidade 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.
É 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 <meta-data> se refere a um arquivo XML que você cria no 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ções 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 arquivo de configurações de serviço de acessibilidade, consulte a seguinte documentação de referência:
android:descriptionandroid:packageNamesandroid:accessibilityEventTypesandroid:accessibilityFlagsandroid:accessibilityFeedbackTypeandroid:notificationTimeoutandroid:canRetrieveWindowContentandroid:settingsActivity
Para mais informações sobre quais configurações podem ser definidas dinamicamente
durante a execução, consulte a documentação de referência AccessibilityServiceInfo.
Configurar o serviço de acessibilidade
Considere o seguinte ao definir as variáveis de configuração para seu serviço de acessibilidade e informar ao sistema como e quando executar:
- A quais tipos de evento você quer que ele responda?
- O serviço precisa estar ativo para todos os apps 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, substitua o método onServiceConnected() e configure seu
serviço lá, 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. Algumas opções de configuração, como canRetrieveWindowContent, só estarão disponíveis se você configurar o serviço usando XML. As opções de configuração 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ê usar XML, referencie-o no manifesto adicionando uma tag <meta-data>
à declaração de serviço, apontando para o arquivo XML. Se você armazenar o arquivo XML em res/xml/serviceconfig.xml, a nova tag vai 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
substituir os seguintes métodos dela. Esses métodos são apresentados na ordem em que o sistema Android os chama: desde o início do serviço (onServiceConnected()) até a execução (onAccessibilityEvent(), onInterrupt()) e o desligamento (onUnbind()).
onServiceConnected(): (opcional) o sistema chama esse método quando se conecta ao serviço de acessibilidade. Use esse método para seguir etapas únicas 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. Se você quiser definir a configuração do serviço durante a execução ou fazer ajustes únicos, esse é um local conveniente para chamarsetServiceInfo().onAccessibilityEvent()(obrigatório): o sistema faz um callback desse método quando detecta umAccessibilityEventque corresponde aos parâmetros de filtragem de eventos especificados pelo serviço de acessibilidade, como quando o usuário toca em um botão ou focaliza um controle da interface do usuário em um app para o qual o serviço de acessibilidade está oferecendo feedback. Quando o sistema chama esse método, ele transmite oAccessibilityEventassociado, 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) o sistema chama esse método quando 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) o sistema chama esse método quando está prestes a desligar 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. Você pode decidir como processar os dados fornecidos pelo sistema Android na forma de objetos AccessibilityEvent e oferecer feedback ao usuário. Para
mais informações 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 das configurações de serviço do serviço de acessibilidade é permitir que você especifique os tipos de eventos de acessibilidade que o serviço pode processar. Especificar essas informações permite que os serviços de acessibilidade cooperem entre si e oferece a flexibilidade de lidar apenas com tipos de eventos específicos de apps específicos. A filtragem de eventos pode incluir os seguintes critérios:
Nomes de pacotes:especifique os nomes dos pacotes dos apps 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 app. É possível definir esse parâmetro nos arquivos de configuração do serviço de acessibilidade com o atributo
android:packageNamesna forma de lista separada por vírgulas ou usar o membroAccessibilityServiceInfo.packageNames.Tipos de evento:especificam os tipos de eventos de acessibilidade que você quer que seu serviço processe. É possível definir esse parâmetro nos arquivos de configuração do serviço de acessibilidade com o atributo
android:accessibilityEventTypesna forma de lista separada pelo caractere|, por exemplo,accessibilityEventTypes="typeViewClicked|typeViewFocused". Ou você pode definir usando o 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 (nível da API 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 (nível da API 26) ou versão mais recente, os usuários podem ativar e desativar o serviço de acessibilidade preferencial em qualquer tela pressionando e mantendo as duas teclas de volume pressionadas ao mesmo tempo. Embora esse atalho ative e desative 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 determinado serviço de acessibilidade pelo atalho, o serviço precisa solicitar o recurso durante a execução.
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 (nível da API 26) e versões mais recentes, 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 de acessibilidade usando o botão de acessibilidade, o serviço precisa adicionar a flag FLAG_REQUEST_ACCESSIBILITY_BUTTON em um atributo android:accessibilityFlags do objeto AccessibilityServiceInfo. O serviço pode registrar callbacks usando
registerAccessibilityButtonCallback().
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 em dispositivos com o Android 8.0 (nível da API 26) e versões mais recentes podem responder a 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_BIOMETRICe o recursoCAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES. - Defina a flag
FLAG_REQUEST_FINGERPRINT_GESTURESno atributoandroid:accessibilityFlags. - 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 método isHardwareDetected(). Mesmo que o dispositivo tenha um sensor de impressão digital, não será possível usar o sensor 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" ... />
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 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 (nível da API 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, conforme 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.
Agir em nome dos usuários
Desde 2011, os serviços de acessibilidade podem agir em nome dos usuários, incluindo mudar o foco de entrada e selecionar (ativar) elementos da interface. Em 2012, 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 até a tela inicial, pressionar o botão "Voltar" e abrir a tela de notificações e a lista de apps recentes. Desde 2012, o Android inclui o foco de acessibilidade, que permite que todos os elementos visíveis sejam selecionados por um serviço de acessibilidade.
Esses 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 requer que o serviço de acessibilidade solicite a ativação do recurso Explorar por toque. O serviço pode solicitar essa
ativação definindo 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 Explorar 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() do serviço e pode responder agindo em
nome do usuário.
Gestos contínuos
Os dispositivos com o Android 8.0 (nível da API 26) ou versões mais recentes são compatíveis com 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 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 agir em nome dos usuários para simplificar as interações com apps e aumentar a produtividade. A capacidade dos serviços de acessibilidade de realizar ações foi adicionada em 2011 e significativamente ampliada em 2012.
Para agir em nome dos usuários, o serviço de acessibilidade precisa se registrar para
receber eventos de apps e solicitar permissão para visualizar o conteúdo deles
definindo 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 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 ações em um app. Se
o serviço precisar realizar uma ação global, como navegar para a tela
inicial, tocar no botão "Voltar" ou abrir a tela de notificações ou a lista de apps
recentes, use o método 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 elemento visível da interface do usuário e realizar ações nele. Esse tipo de foco é diferente do foco de entrada, que determina qual elemento da interface do usuário na tela recebe entradas quando um usuário digita caracteres, pressiona Enter em um teclado ou o botão central de um controle direcional.
É possível que um elemento em uma interface do usuário tenha foco de entrada enquanto outro elemento tenha foco de acessibilidade. O objetivo do foco de acessibilidade é oferecer aos serviços de acessibilidade um método de interação com elementos visíveis em uma tela, independente de o elemento estar com foco de entrada do ponto de vista do sistema. Para garantir que o serviço de acessibilidade interaja corretamente com os elementos de entrada dos apps, siga as diretrizes para testar a acessibilidade de um app e teste seu serviço usando um app comum.
Um serviço de acessibilidade pode determinar qual elemento da interface do usuário tem 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 de acessibilidade pode
definir o foco de acessibilidade usando o
método
performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS).
Coletar informações
Os serviços de acessibilidade 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 mudanças de janelas
O Android 9 (nível 28 da API) 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 mudam. 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 definir títulos do painel de acessibilidade para os objetos View,
o serviço poderá reconhecer quando a interface do app for atualizada. Quando um evento
TYPE_WINDOW_STATE_CHANGED ocorrer, use os tipos retornados por
getContentChangeTypes() para determinar como a janela muda. Por
exemplo, o framework pode detectar quando um painel tem um novo título ou quando ele
desaparece.
Ver detalhes de evento
O Android fornece informações para serviços de acessibilidade sobre a interação com a interface do usuário
por meio de objetos AccessibilityEvent. Em versões anteriores do Android,
as informações disponíveis em um evento de acessibilidade, embora oferecessem detalhes
significativos 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 entender o 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 um usuário que quer agendar uma reunião.
Desde 2011, o Android 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ções. Uma hierarquia de visualizações é 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 Android pode oferecer mais detalhes sobre os eventos de acessibilidade, permitindo que os serviços ofereçam feedback mais útil aos usuários.
Um serviço de acessibilidade 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, incluindo o tipo de objeto que está sendo usado, o texto descritivo e
outros detalhes.
AccessibilityEvent.getRecordCount()egetRecord(int): esses métodos permitem recuperar o conjunto de objetosAccessibilityRecordque contribuem para oAccessibilityEventtransmitido a você pelo sistema. Esse nível de detalhamento fornece mais contexto para o evento que aciona o serviço de acessibilidade.AccessibilityRecord.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 origina 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.
A plataforma Android permite que um AccessibilityService consulte a hierarquia de visualizações, coletando informações sobre o componente de IU que gera um evento, bem como sobre o pai e os filhos dele. 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ó retorna 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.
No exemplo a seguir, o código faz o seguinte quando um evento é recebido:
- Captura imediatamente a visualização mãe em que o evento foi originado.
- Nessa visualização, procure um rótulo e uma caixa de seleção como visualizações secundárias.
- se elas forem encontradas, cria uma string para informar o usuário, indicando o rótulo e se ele foi verificado.
Se um valor nulo for retornado a qualquer momento durante a transferência da hierarquia de visualizações, 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 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. Configure como
ele interage com o usuário adicionando o mecanismo de conversão de texto em voz do Android 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 (nível da API 28) introduz vários recursos que dão acesso a
dicas na interface 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
Desde 2017, o Android 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ó. getHintText()fornece acesso ao texto de dica. Mesmo que um objeto não esteja exibindo texto de dica, chamargetHintText()funciona.
Locais de caracteres de texto na tela
Em dispositivos com o Android 8.0 (nível da API 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 transmitindo
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 da interface pode assumir
um intervalo de valores. Ao criar um intervalo usando RangeInfo.obtain() ou
ao recuperar os valores extremos do intervalo usando getMin() e
getMax(), lembre-se de que os dispositivos com o Android 8.0 (nível da API 26)
e versões mais recentes representam intervalos unilaterais de forma padronizada:
- Para intervalos sem mínimo,
Float.NEGATIVE_INFINITYrepresenta o valor mínimo. - Para intervalos sem máximo,
Float.POSITIVE_INFINITYrepresenta 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 chegar. 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 aciona 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: