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. Esses serviços são executados em segundo plano e se comunicam com o sistema para inspecionar o conteúdo da tela e interagir com os apps em nome do usuário. Por exemplo, leitores de tela (como o TalkBack), ferramentas de acesso com interruptor e sistemas de controle de voz.
Este guia aborda os conceitos básicos da criação de um serviço de acessibilidade do Android.
Ciclo de vida do serviço de acessibilidade
Para criar um serviço de acessibilidade, é necessário estender a classe
AccessibilityService
e declarar o serviço no manifesto do app.
Criar a classe de serviço
Crie uma classe que estenda o AccessibilityService. Substitua os seguintes métodos:
onAccessibilityEvent: é chamado quando o sistema detecta um evento que corresponde à configuração do seu serviço (por exemplo, mudança de foco ou clique em um botão). É aqui que seu serviço interpreta a interface do usuário.onInterrupt: chamado quando o sistema interrompe o feedback do seu serviço (por exemplo, para interromper a saída de voz quando o usuário move o foco rapidamente).
package com.example.android.apis.accessibility import android.accessibilityservice.AccessibilityService import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.FingerprintGestureController import android.accessibilityservice.AccessibilityButtonController import android.accessibilityservice.GestureDescription import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.graphics.Path import android.os.Build import android.media.AudioManager import android.content.Context class MyAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { // Interpret the event and provide feedback to the user } override fun onInterrupt() { // Interrupt any ongoing feedback } override fun onServiceConnected() { // Perform initialization here } }
Declarar no manifesto
Registre o serviço no arquivo AndroidManifest.xml. É necessário aplicar estritamente a permissão BIND_ACCESSIBILITY_SERVICE para que somente o sistema possa se vincular ao serviço.
Para garantir que o botão de configurações funcione, declare o ServiceSettingsActivity.
<application> <service android:name=".accessibility.MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="true" android:label="@string/accessibility_service_label"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> <activity android:name=".accessibility.ServiceSettingsActivity" android:exported="true" android:label="@string/accessibility_service_settings_label" /> </application>
Configurar o serviço
Crie um arquivo de configuração em res/xml/accessibility_service_config.xml. Esse
arquivo define quais eventos seu serviço processa e qual feedback ele fornece.
Não se esqueça de referenciar o ServiceSettingsActivity que você declarou no
manifesto:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagDefault|flagRequestFingerprintGestures|flagRequestAccessibilityButton" android:accessibilityFeedbackType="feedbackSpoken" android:notificationTimeout="100" android:canRetrieveWindowContent="true" android:canPerformGestures="true" android:settingsActivity="com.example.android.apis.accessibility.ServiceSettingsActivity" />
O arquivo de configuração inclui os seguintes atributos principais:
android:accessibilityEventTypes: os eventos que você quer receber. UsetypeAllMaskpara um serviço de uso geral.android:canRetrieveWindowContent: precisa sertruese o serviço precisar inspecionar a hierarquia da interface (por exemplo, para ler texto da tela).android:canPerformGestures: precisa sertruese você pretende enviar gestos (como deslizar ou tocar) de forma programática.android:accessibilityFlags: combine flags para ativar recursos. OflagRequestFingerprintGesturesé necessário para gestos de impressão digital. OflagRequestAccessibilityButtoné necessário para o botão de acessibilidade do software.
Para uma lista completa de opções de configuração, consulte
AccessibilityServiceInfo.
Configuração do ambiente de execução
Embora a configuração XML seja estática, também é possível modificar a configuração do serviço dinamicamente no tempo de execução. Isso é útil para ativar/desativar recursos com base nas preferências do usuário.
Substitua onServiceConnected() para aplicar atualizações de ambiente de execução usando
setServiceInfo():
override fun onServiceConnected() { val info = AccessibilityServiceInfo() // Set the type of events that this service wants to listen to. info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED // Set the type of feedback your service provides. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN // Set flags at runtime. info.flags = AccessibilityServiceInfo.FLAG_DEFAULT or AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES this.setServiceInfo(info) }
Interpretar conteúdo da interface
Quando onAccessibilityEvent() é acionado, o sistema fornece um
AccessibilityEvent. Esse evento funciona como o ponto de entrada para a árvore de acessibilidade, uma representação hierárquica do conteúdo da tela.
Seu serviço interage principalmente com objetos
AccessibilityNodeInfo
que representam elementos da interface, como botões, listas e texto. Os dados sobre esses elementos de interface são normalizados em AccessibilityNodeInfo.
O exemplo a seguir mostra como recuperar a origem de um evento e percorrer a árvore de acessibilidade para encontrar informações.
override fun onAccessibilityEvent(event: AccessibilityEvent) { // Get the source node of the event val sourceNode: AccessibilityNodeInfo? = event.source if (sourceNode == null) return // Inspect properties if (sourceNode.isCheckable) { val state = if (sourceNode.isChecked) "Checked" else "Unchecked" val label = sourceNode.text ?: sourceNode.contentDescription // Provide feedback (for example, speak to the user) speakToUser("$label is $state") } // Always recycle nodes to prevent memory leaks sourceNode.recycle() } private fun speakToUser(text: String) { // Your text-to-speech implementation goes here }
Agir em nome dos usuários
Os serviços de acessibilidade podem realizar ações em nome do usuário, como clicar em botões ou rolar listas.
Para realizar uma ação, chame performAction() em um objeto AccessibilityNodeInfo.
fun performClick(node: AccessibilityNodeInfo) { if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
Para ações globais que afetam todo o sistema (como pressionar o botão "Voltar"
ou abrir a bandeja de notificações), use performGlobalAction().
// Navigate back fun navigateBack() { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) }
Gerenciar foco
O Android tem dois tipos distintos de foco: o foco de entrada (onde a entrada do teclado é direcionada) e o foco de acessibilidade (o que o serviço de acessibilidade está inspecionando).
O snippet a seguir mostra como encontrar o elemento que tem o foco de acessibilidade no momento:
// Find the node that currently has accessibility focus // Note: rootInActiveWindow can be null if the window is not available val root = rootInActiveWindow if (root != null) { val focusedNode = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) // Do something with focusedNode // Always recycle nodes focusedNode?.recycle() // rootInActiveWindow doesn't need to be recycled, but obtained nodes do. }
O snippet a seguir mostra como mover o foco de acessibilidade para um elemento específico:
// Request that the system give focus to a given node fun focusNode(node: AccessibilityNodeInfo) { node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) }
Ao criar um serviço de acessibilidade, respeite o estado de foco do usuário e evite roubar o foco, a menos que seja explicitamente acionado por uma ação do usuário.
Fazer gestos
Seu serviço pode enviar gestos personalizados para a tela, como deslizar, tocar
ou interações multitoque. Para isso, declare
android:canPerformGestures="true" na configuração para usar
a API dispatchGesture().
Gestos simples
Para realizar gestos simples, comece criando um objeto Path para representar o
movimento associado a um determinado gesto. Em seguida, envolva o Path em um
GestureDescription para descrever o traço. Por fim, chame dispatchGesture
para enviar o gesto.
fun swipeRight() { // Create a path for the swipe (from x=100 to x=500) val swipePath = Path() swipePath.moveTo(100f, 500f) swipePath.lineTo(500f, 500f) // Build the stroke description (0ms delay, 500ms duration) val stroke = GestureDescription.StrokeDescription(swipePath, 0, 500) // Build the gesture description val gestureBuilder = GestureDescription.Builder() gestureBuilder.addStroke(stroke) // Dispatch the gesture dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { super.onCompleted(gestureDescription) // Gesture finished successfully } }, null) }
Gestos contínuos
Para interações complexas (como desenhar um formato de L ou realizar um arrasto preciso
de várias etapas), é possível encadear traços usando o parâmetro willContinue.
fun performLShapedGesture() { val path1 = Path().apply { moveTo(200f, 200f) lineTo(400f, 200f) } val path2 = Path().apply { moveTo(400f, 200f) lineTo(400f, 400f) } // First stroke: willContinue = true val stroke1 = GestureDescription.StrokeDescription(path1, 0, 500, true) // Second stroke: continues immediately after stroke1 val stroke2 = stroke1.continueStroke(path2, 0, 500, false) val builder = GestureDescription.Builder() builder.addStroke(stroke1) builder.addStroke(stroke2) dispatchGesture(builder.build(), null, null) }
Gerenciamento de áudio
Ao criar um serviço de acessibilidade (principalmente um leitor de tela), use o stream de áudio
STREAM_ACCESSIBILITY. Isso permite que os usuários controlem o volume do serviço
independente do volume de mídia do sistema.
fun increaseAccessibilityVolume() { val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_ACCESSIBILITY, AudioManager.ADJUST_RAISE, 0 ) }
Inclua a flag FLAG_ENABLE_ACCESSIBILITY_VOLUME na sua
configuração, em XML ou usando setServiceInfo no tempo de execução.
Recursos avançados
Gestos de impressão digital
Em dispositivos com o Android 10 (nível 29 da API) ou mais recente, seu serviço pode capturar deslizamentos direcionais no sensor de impressão digital. Isso é útil para fornecer controles de navegação alternativos.
Adicione a seguinte lógica ao método onServiceConnected():
// Import: android.os.Build // Import: android.accessibilityservice.FingerprintGestureController private var gestureController: FingerprintGestureController? = null override fun onServiceConnected() { // Check if the device is running Android 10 (Q) or higher if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { gestureController = fingerprintGestureController val callback = object : FingerprintGestureController.FingerprintGestureCallback() { override fun onGestureDetected(gesture: Int) { when (gesture) { FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN -> { // Handle swipe down } FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP -> { // Handle swipe up } } } } gestureController?.registerFingerprintGestureCallback(callback, null) } }
Botão de acessibilidade
Em dispositivos que usam teclas de navegação de software, os usuários podem invocar seu serviço usando um botão de acessibilidade na barra de navegação.
Para usar esse recurso, adicione a flag FLAG_REQUEST_ACCESSIBILITY_BUTTON às configurações de serviço. Em seguida, adicione a lógica de registro ao método
onServiceConnected().
// Import: android.accessibilityservice.AccessibilityButtonController override fun onServiceConnected() { // ... existing initialization code ... val controller = accessibilityButtonController controller.registerAccessibilityButtonCallback( object : AccessibilityButtonController.AccessibilityButtonCallback() { override fun onClicked(controller: AccessibilityButtonController) { // Respond to button tap } } ) }
Conversão de texto em voz multilíngue
Um serviço que lê texto em voz alta pode mudar de idioma automaticamente se o texto de origem estiver marcado com LocaleSpan. Isso permite que o serviço pronuncie corretamente
conteúdo em vários idiomas sem troca manual.
import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.LocaleSpan import java.util.Locale // Wrap text in LocaleSpan to indicate language val spannable = SpannableStringBuilder("Bonjour") spannable.setSpan( LocaleSpan(Locale.FRANCE), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )
Quando o serviço processa AccessibilityNodeInfo, inspecione a propriedade text
para objetos LocaleSpan e determine o idioma correto de conversão de texto em voz.
Outros recursos
Para saber mais, consulte os seguintes recursos: