В Android 13 Google ограничит доступ к спец. возможностям
В Google решили ограничить доступ к службам специальных возможностей (API AccessibilityService) приложениям, которые загружены из неизвестных источников. Такое изменение появится на устройствах под управлением Android 13. Выясняем: что это может означать для пользователей с нарушением зрения.
✅ Ограничение обусловлено соображениями безопасности пользователей.
Дело в том, что приложения, обращающиеся к специальным возможностям, получают гораздо больше прав на устройстве, чем обычные приложения. В частности, специальные возможности дают приложению доступ к информации, отображаемой на экране устройства. Эта возможность предназначена для программ экранного доступа для пользователей с нарушением зрения, например: TalkBack и Jieshuo (Commentary) Screenreader. Но и приложения, модифицированные злоумышленниками, могут, пользуясь правами специальных возможностей, отслеживать информацию, появляющуюся на экране, включая личные и платёжные данные. только в отличии от программы экранного доступа, вовсе не с целью озвучить данные для пользователя. Причём владелец устройства может и не подозревать о такой активности приложения.
✅ Ограничение коснётся только приложений, загруженных из неизвестных источников.
На устройство с системой Android пользователь может устанавливать приложения двумя способами:
- Загружать из официальных магазинов приложений (Google Play, F-droid, Samsung Galaxy Store и др.). Считается, что перед публикацией в магазине проводится проверка приложений на безопасность.
- Скачивать файл с расширением APK с других интернет-ресурсов. Такие приложения Google называет загруженными из неизвестных или ненадёжных источников. Есть опасность, что приложение могло быть изменено злоумышленниками.
Популярную среди незрячих пользователей Android-устройств программу экранного доступа Jieshuo (Commentary) Screenreader приходится устанавливать как раз вторым способом.
✅ Кстати, рассматриваемое ограничение — не единственное. Как сообщает сайт о технологиях 4PDA, Google с 11 мая ограничивает доступ к API AccessibilityService сторонним приложениям для записи телефонных разговоров. В прочем, опция записи звонков остаётся легальной для приложений, предустановленных производителями устройств.
Отметим, что пока нет подробностей, сложно с уверенностью говорить о том, как эти изменения отразятся на незрячих пользователях будущей версии системы Android (Android 13). Тем не менее, мы обратились за комментарием к Сергею Екатеринушкину — преподавателю, обучающему начинающих пользователей с нарушением зрения (г. Тамбов).
Комментарий Сергея Екатеринушкина
«Что касается записи звонков: смартфоны фирм Huawei и Xiaomi могут записывать телефонные разговоры через стандартное приложение «Диктофон» либо непосредственно в звонилки, то есть в приложение «Телефон». Китайские производители модифицировали эти приложения таким образом, чтобы они могли записывать звонки, и встроили их в систему. Так что подобные ограничения, вводимые компанией Google, могут быть обойдены производителями устройств на уровне модификации системы.
Что касается основного вопроса о специальных возможностях, то китайский Commentary screenreader работает, например, и на смартфонах Samsung. В устройствах Samsung свой набор сервисов отвечающих за спец. возможности. Приэтом сторонние приложения использующие API AccessibilityService, не могут их задействовать. Так, в тех местах где активируется элемент обычным двойным касанием, например, при срабатывании стандартного будильника, с Commentary screenreader приходится провести двумя пальцами снизу вверх либо в всторону.
Ввод текста на фирменной клавиатуре Samsung тоже не даёт для Commentary весь свой функционал. Так при наборе текста с Commentary и клавиатурой Samsung нужно включить отображение клавиши смены раскладки (Глобус) в настройках самой клавиатуры. Другая проблема — это отсутствие функции «Ввод одним касанием», когда мы отпускаем символ, чтобы его ввести. Приходится вводить букву двойным касанием, как активируем обычный элемент управления на экране. Эти мелкие неудобства обходятся установкой клавиатуры Google.
Также, на некоторых моделях Samsung фирменный браузер Samsung internet не даёт Commentary читать web-странички. Но ведь Android — открытая ОС. Есть альтернативы в виде браузеров Chrome и FireFox.
Таким образом, можно сделать вывод, что возможны небольшие неудобства, с которыми столкнутся незрячие пользователи при обновлении до Android 13. Уверен, что часть проблем решиться заменой некоторых стандартных приложений, таких как клавиатура, либо использованием новых жестов, либо разработчики Commentary screenreader со временем исправят возникшие проблемы, а активное сообщество пользователей в этом поможет.
А если вы — начинающий пользователь, то переждите этот период адаптации и не обновляйте своё устройство до тех пор, пока не решатся критичные для вас проблемы. Но паниковать точно не стоит», — заключает Сергей Екатеринушкин.
Хотите оперативно узнавать о новых публикациях? Присоединяйтесь к нашей группе в WhatsApp.
Благодарим Вас за внимание! Пожалуйста, поделитесь с друзьями в соцсетях.
Контроль над Android. Перехватываем данные и сбрасываем настройки смартфона через самые опасные API


Кроме традиционных разрешений, в Android есть три мета‑разрешения, которые открывают доступ к весьма опасным API, позволяющим в прямом смысле захватить контроль над устройством. В этой статье мы научимся их использовать, чтобы программно нажимать кнопки смартфона, перехватывать уведомления, извлекать текст из полей ввода других приложений и сбрасывать настройки смартфона.
О каких API пойдет речь?
- Администрирование устройства — API, предназначенный для корпоративных приложений. Позволяет сбрасывать и устанавливать пароль экрана блокировки, сбрасывать смартфон до заводских настроек и устанавливать правила минимальной сложности пароля. Одна из особенностей API — запрещено удалять приложения, получившие права администратора, чем с радостью пользуются авторы зловредных приложений.
- Accessibility — API для реализации приложений, ориентированных на людей с ограниченными возможностями. Фактически API позволяет создавать альтернативные способы управления устройством и поэтому открывает поистине огромный простор для злоупотребления. С его помощью можно получить доступ к содержимому экрана практически любого приложения, нажимать кнопки интерфейса и программно нажимать клавиши самого смартфона. Но есть и способ защиты: разработчик приложения может прямо указать, что определенные элементы интерфейса приложения будут недоступны для сервисов Accessibility.
- Уведомления — API, позволяющий получить доступ ко всем уведомлениям, которые отображаются в панели уведомлений. С помощью этого API приложение может прочитать всю информацию об уведомлении, включая заголовок, текст и содержимое кнопок управления, нажать на эти кнопки и даже смахнуть уведомление. API пользуется особой популярностью среди разработчиков всевозможных банковских троянов, с помощью которого они могут читать коды подтверждения и смахивать предупреждающие сообщения от банков.
Получив доступ ко всем этим API, зловредное приложение сможет сделать со смартфоном практически все что угодно. Именно поэтому для их защиты используются не традиционные запросы полномочий, на которые пользователь может машинально ответить «Да», а скрытый глубоко в настройках интерфейс, который при активации покажет угрожающее сообщение. Все, что может сделать приложение, чтобы получить нужное полномочие, — это перебросить пользователя в окно настроек, после чего тот должен будет найти нужное приложение, включить напротив него переключатель и согласиться с предупреждающим сообщением.
Заставить пользователя дать разрешение на использование этих API можно обманом. Зачастую зловреды прикидываются легитимными приложениями, которым разрешение нужно для работы ключевой функциональности. К примеру, это может быть приложение для ведения журнала уведомлений или приложение для альтернативной жестовой навигации (такому приложению нужен сервис Accessibility для нажатия кнопок навигации). Также можно использовать атаку Cloak & Dagger, чтобы перекрыть окно настроек другим безобидным окном.
Нажимаем кнопки смартфона
Простейший сервис Accessibility может выглядеть так (код на Kotlin):
Чтобы система узнала о нашем сервисе, его необходимо объявить в AndroidManifest.xml :
Это описание ссылается на конфигурационный файл accessibility_service_config.xml , который должен быть определен в каталоге xml проекта. Для нашего случая достаточно будет такого конфига:
После того как пользователь включит наш сервис Accessibility в окне «Настройки → Спец. возможности», система автоматически запустит сервис и мы сможем выполнить функцию pressHome() , чтобы нажать кнопку «Домой»:
// Если service не null — значит, система успешно запустила сервисif (AccessService.service != null)
Одной лишь только этой функциональности достаточно, чтобы реализовать Ransomware, который будет вызывать функцию pressHome() в цикле и бесконечно возвращать пользователя на домашний экран, не давая нормально использовать устройство.
![]() |
| Окно включения сервиса Accessibility в Android 11 |
Однако настоящая мощь Accessibility кроется не в нажатии кнопок навигации, а в возможности контролировать другие приложения.
Перехватываем содержимое полей ввода
API Accessibility был создан для людей с ограниченными возможностями. С его помощью можно, например, создать приложение, которое будет зачитывать все надписи интерфейса и позволит нажимать элементы интерфейса голосом. Все это достижимо благодаря тому, что Accessibility дает полный доступ к интерфейсу приложений в виде дерева элементов: можно пройти по нему и выполнить над элементами определенные операции.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Адаптируем Android приложение для незрячих людей. Часть 1: основы
В один из зимних вечеров я сидел дома, читал замечательную книжку «Android-программирование для профессионалов» и наткнулся на главу про Accessibility.
Раньше я об этой теме не задумывался: клал null в поле contentDescription и жил себе спокойно. Но когда получил поверхностные знания, как слабовидящие люди пользуются смартфоном через утилиту TalkBack, у меня появилось желание погрузиться в тему доступности глубже.
Меня зовут Тимур Задворнов, я Android-разработчик в Surf. Расскажу и покажу, как работает Accessibility в Android. Посмотрим, легко ли адаптировать приложения для людей с ограниченными возможностями — или это слишком трудоёмкая задача, к которой даже не стоит подступаться.

Погружаться в тему Accessibility я начал с гуглежа статистики. По данным ВОЗ, в мире более миллиарда человек обладает с той или иной формой инвалидности. «Немало», — подумал я и сразу задался другим вопросом: а почему, если в мире так много людей с инвалидностью, огромное количество популярных и не очень приложений не поддерживают приложения для слабовидящих, глухих, людей с заболеваниями опорно-двигательного аппарата? Решил, что надо исправлять это недоразумение и двигать Accessibility в массы.
Для начала разберемся, что вообще такое Accessibility

Accessibility в переводе с английского — «доступность». В контексте программного обеспечения — разработка ПО таким образом, чтобы им могли пользоваться люди с ограниченными возможностями. У термина есть общепринятое сокращение — a11y: между a и y в слове accessibility 11 букв.
Чтобы разобраться, как работает Accessibility, предлагаю задаться вопросом: а как в целом люди пользуются смартфоном? Пользователь взаимодействует с устройством, передавая ему действия: нажатия, свайпы, потряхивания. Смартфон возвращает результат действия, отображая его на экране.
Мы взаимодействуем со смартфоном как привыкли: нажимаем на экран и получаем результат действия. Люди с ограниченными возможностями пользуются смартфоном точно так же, просто они передают и получают информацию со смартфона другими путями: отправляют голосовые команды, команды с другого устройства ввода. Способов много — подход общий.

В Android существуют встроенные Accessibility Services: TalkBack, Switch Access, Voice Access и прочие. О них можно думать как о плагинах для Android, которые помогают людям с ограниченными возможностями пользоваться смартфоном.
Под термином «Accessibility Services» будут подразумеваться перечисленные сервисы, а не Android-сервисы для работы в бэкграунде. Пишу об этом, потому что в Android есть одноименный сервис — AccessibilityService, который работает в бэкграунде с отправляемыми ему AccessibilityEvent.
Какие Accessibility Services существуют
С каждой новой версией Android выкатывает всё больше новых сервисов для пользователей с ограниченными возможностями. Сейчас в Android есть:
- TalkBack — зачитывает информацию о UI-компонентах;
- Switch Access — управление устройством через специальный контроллер;
- Voice Access — голосовое управление смартфоном;
- Select to Speak — зачитывает текстовый контент в выделенной области;
- Extra Dim — настройки яркости дисплея;
- Magnification — экранная лупа;
- Live Captions — экранные субтитры;
- Live Transcribe — перевод речи в текст;
- Sound Amplifier — управление звуком;
- Lookout — распознавание объектов через камеру;
- Action Blocks — создание сценариев использования.
И это ещё, возможно, не всё!
Остановимся подробнее на первых трёх сервисах: TalkBack, Switch Access, Voice Access. Их используют чаще всего: именно с ними придётся сталкиваться разработчику в повседневной работе. Остальные — вспомогательные. Более подробно про них можно посмотреть в официальной документации.
TalkBack
TalkBack — утилита, которая зачитывает информацию с экрана. Помогает слабовидящим людям пользоваться смартфоном.
TalkBack меняет логику управления смартфоном: пользователь выделяет нужный компонент, и система зачитывает содержимое. Компоненты экрана можно выделять либо тапом, свайпами влево и вправо или зажать палец на экране и двигать его — тогда TalkBack будет зачитывать каждый элемент, на который наведён жест. Свайп вправо — выделится следующий элемент на экране, свайп влево — предыдущий.
Все привычные обычному пользователю жесты — назад, свернуть, меню многозадачности — работают, но их нужно воспроизводить двумя пальцами. Все действия и жесты TalkBack описаны в документации.
На каждом смартфоне с Android на борту можно включить TalkBack и пройти туториал по использованию: всё это делается в настройках, пункт Accessibility.
В настройках TalkBack есть меню с настройками для разработчиков. Можно включить субтитры или добавить шорткат быстрого включения и выключения TalkBack — зажать кнопки громкости на несколько секунд. Очень помогает во время разработки.
Switch Access
Switch Access — сервис для управления смартфоном с помощью специального устройства, переключателя.

Принцип взаимодействия со смартфоном схож с принципом работы через TalkBack. На экране выделен компонент, между компонентами можно переключаться. Обычно на переключателе есть несколько кнопок. В качестве переключателя можно использовать специальное устройство или настроить стороннюю клавиатуру, а также кнопки громкости на смартфоне для удобного дебаггинга.
Voice Access
Voice Access — сервис для голосового управления смартфоном. Пользователь говорит, какое действие нужно сделать на экране: нажать кнопку «Лайк», пролистать вниз, ввести текст в поле «Сообщение». Действие выполняется. На видео — Voice Access в действии.
На самом деле Voice Access далеко не идеальная вещь: иногда тупит и некорректно распознаёт слова — особенно названия приложений.
Что происходит со стороны разработчиков
Давайте посмотрим, какие инструменты есть для разработчиков, чтобы UX приложений не страдал. Хорошие новости: все компоненты из стандартной библиотеки Android по умолчанию поддерживают Accessibility. Не придётся с нуля разбираться и делать доступными базовые компоненты: TextView, EditText, Switch и другие.
Приложение Accessibility Scanner для помощи разработчикам
Google выпустил для разработчиков приложение Accessibility Scanner: оно помогает найти некомфортные для людей с ограниченными возможностями места в приложении.
Accessibility Scanner работает просто:

- Открываем, даём все разрешения, оно отображается над всеми приложениями как плавающая кнопка.
- Заходим в приложение, которое нужно проверить, переходим на нужный экран, нажимаем на кнопку Snapshot.
- Ждём, пока Accessibility Scanner проанализирует экран, и смотрим на результат.
Accessibility Scanner — отличная штука, но у него есть существенный недостаток. Сканер находит только очевидные проблемы: размер текста, размер кликабельной области компонента, отсутствие лейбла у изображения и так далее. В более сложных случаях он бесполезен. Например, не поможет с распознаванием кастомных вью и правильной интерпретацией для слабовидящих: что зачитывать с этого компонента, какие действия с ним можно совершить.
Давайте разберемся, как сделать приложение более доступным с помощью нескольких простых шагов: они закроют процентов 90 всевозможных кейсов.
Шрифты
Правило простое: всегда указывайте размер шрифтов в sp! Вы меня спросите: «Зачем для шрифтов придумали какую-то отдельную единицу? Если я укажу размер в dp, никто не умрёт».

Если пользователь увеличит шрифты в системе, нужно, чтобы они увеличились и в приложении. Для этого и пригодится sp-юнит: если указать размер текста 16sp, а пользователь в системе поставил коэффициент увеличения текста = 1,25, то размер шрифта станет 20dp. На иллюстрациях видно, как изменяется размер шрифта, указанный в sp.

Заголовки
TalkBack предоставляет удобную навигацию между заголовками. Если на экране много текста с заголовками или, например, на экране есть список с подсписками, которые также помечены заголовками, в эти TextView дополнительно следует положить true в поле accessibilityHeading . Тогда текст будет считываться как заголовок.
Этот параметр появился в Android 9. Если нужно использовать его на более старых версиях, эту проблему решает compat-версия этого параметра — ViewCompat.setAccessibilityHeading(view, boolean) .
Контраст
Люди с нарушениями зрения могут воспринимать цвета по-другому: стоит следить, все ли элементы на экране чётко различимы между собой, есть ли цветовой контраст. По гайдлайнам Android, хорошая практика — устанавливать цветовой контраст на наложенных друг на друга компонентах с соотношением более чем 3:1 для больших текстов и 4,5:1 для остального контента.
Что это за цифры? Это соотношение между цветами, которое показывает, насколько сильно контрастируют два цвета между собой. Чтобы было понятно, разберем на примере: соотношение белого цвета (#FFFFFF) к чёрному (#000000) равняется 21:1 — это максимальное возможное соотношение. Два одинаковых цвета имеют отношение 1:1. Проверить контраст между цветами можно на сайте WebAIM.

КАПС — НЕ НАШ БРО
На некоторых девайсах TalkBack читает текст, написанный заглавными буквами, как аббревиатуру – по букве. Сами понимаете, воспринимать это на слух очень трудно.
Заглавными буквами следует писать только аббревиатуры. Основной текст — строчными буквами или sentence case. Если всё-таки нужно оставить текст заглавными буквами, используйте параметр android:textAllCaps=”true” — и тогда всё будет ок.
Размеры элементов
Любые кликабельные элементы на экране должны иметь размер как минимум 48x48dp. Такой размер рекомендован Google: область нажатия пальцем на экран равняется приблизительно этому значению. В вебе, например, рекомендованный размер 44x44px.
Content Description
Не будем лукавить: все мы когда-то устанавливали поле contentDescription в null , потому что «да зачем оно мне надо». Но если вы хотите, чтобы приложение было доступным, придётся разбираться, что это за поле за такое.
Поле contentDescription используется в основном для визуальных компонентов без текста — ImageButton , ImageView и так далее. Это нужно, чтобы TalkBack при наведении на этот элемент не зачитал «Unlabeled, Button, double-tap to activate», а дал конкретную информацию: что за элемент, зачем он нужен на экране и что произойдет, если нажать на него. Эта строка обязательно должна быть локализована: если пользователь из Франции запустит TalkBack, а ему на чистом японском зачитают «дескрипшен», вряд ли кому-то от этого станет легче. И вообще: любые строки, связанные с a11y, будь то contentDescription или дополнительные действия TalkBack, о которых мы поговорим далее, должны быть локализованы.
В этой статье я буду использовать английскую версию TalkBack и локализованные строки так же на английском языке. Не беспокойтесь: на русском TalkBack тоже работает.
Если ImageView имеет на экране чисто декоративную роль, contentDescription можно не указывать. Тогда важно указать другой параметр — importantForAccessibility . Он отвечает за необходимость прочтения TalkBack содержимого этого компонента. У этого параметра есть значения:
- yes — компонент обязателен для прочтения TalkBack.
- no — компонент пропускается.
- noHideDescendants — компонент и его дочерние компоненты пропускаются.
- auto — система сама определяет, обязателен ли компонент для TalkBack. Это значение по умолчанию.
Live Regions
Иногда нужно при действии с одним компонентом на экране обновить значение компонента, не находящегося в фокусе. Для этого у View в Android есть поле accessibilityLiveRegion , которое позволяет зачитать значение из вью, если оно было изменено. У accessibilityLiveRegion есть три возможных значения:
- none — ничего не обновлять, значение по умолчанию.
- polite — если значение обновилось, но TalkBack ещё воспроизводит старое значение, новое значение зачитается после того, как TalkBack закончит говорить.
- assertive — противоположное значению polite . При обновлении значения TalkBack перестанет зачитывать старое значение и сразу начнёт зачитывать новое.
Проще понять на примере: у нас есть кнопка Increment, которая увеличивает значение на 1 и отображает новое значение в TextView. Изменяем значение accessibilityLiveRegion у TextView на polite и вот какой будет результат.
Порядок навигации между View
В идеале порядок должен идти слева направо, сверху вниз, но иногда этот порядок приходится менять для более удобной навигации между компонентами. На картинке ниже — как раз такой пример. Он надуманный, это можно сделать гораздо лучше: объединить заголовок и значение или использовать параметр labelFor .

На этом примере хотелось бы, чтобы после чтения заголовка читалось значение, для которого предназначен этот заголовок. Чтобы изменять порядок навигации, у всех View есть поле accessibilityTraversalBefore . В него нужно передать идентификатор компонента, на который мы хотим переключаться после фокуса текущей выделенной вью.
Также у View есть противоположный параметр — accessibilityTraversalAfter . В нём лежит идентификатор компонента, после которого текущий компонент будет выделен TalkBack. Иногда случается, что нужно задать параметр динамически через код: для этого у объекта класса AccessibilityNodeInfo вызвать метод setTraversalBefore или setTraversalAfter . Подробнее про AccessibilityNodeInfo и AccessibilityDelegate расписано далее.
Давайте укажем к каждому TextView с заголовком параметр android:accessibilityTraversalBefore=»@id/value_textview» , где @id/value_textview — идентификатор соответствующего заголовка TextView со значением. Получим следующий порядок навигации:

Кастомные View
Обычно кастомные вью состоят из нескольких более простых компонентов: текстов, кнопок, свитчеров. Хорошая практика — делать так, чтобы компонент выделялся TalkBack как единый, а не выделял отдельно компоненты-составные, и предлагал какие-то действия что можно сделать с этим сложным компонентом.
Понятнее будет, если разобраться на примерах. Предположим, есть компонент деталей криптокошелька. Возможные составляющие: название, баланс, адрес, кнопки пополнить и перевести.

По умолчанию, если ничего не делать, TalkBack просто зачитает текстовое содержимое компонента: название кошелька, баланс и адрес.

Как можно улучшить UX для незрячих? Сделаем компонент более доступным за несколько простых шагов.
1. Все дочерние элементы пометить importantForAccessibility = no , чтобы TalkBack не читал ничего, кроме кнопок «пополнить» и «перевести». С ними разберёмся позже.
2. Даём корневому элементу понятный contentDescription . Нужно определить, какая информация полезна для прочтения TalkBack. Пользователю будет полезно знать название кошелька и баланс. Обновлять contentDescription будем в функции setData , которая вызывается при инициализации компонента.
fun setData( title: String, balance: String, address: String ) = with(binding) < titleTv.text = title balanceTv.text = balance addressTv.text = address // Строка со значением "Wallet card: %s, balance is %s" root.contentDescription = context.getString( R.string.wallet_card_accessibility_description, title, balance ) >
3. Обрабатываем взаимодействие с компонентом. Какой смысл этого компонента? Что он делает? Выделим список возможных действий:
- По нажатию на карточку переходим на экран деталей по кошельку.
- По нажатию на кнопку «Перевести» или «Пополнить» переходим на экран переводов. С кнопками делать ничего не нужно, мы их не помечали как игнорируемые и они по умолчанию поддерживают accessibility.
- Возможность скопировать адрес кошелька.
- Возможность отобразить адрес в формате QR-кода.
Для перехода к деталям кошелька почти ничего не требуется, если уже назначен слушатель на событие нажатия на карточку. Единственное, что стоит переопределить, — название действия. По умолчанию при нажатии на кликабельный элемент TalkBack будет зачитывать «Double tap to activate» Это понятно, если элемент — кнопка или свитчер. Но если компонент сложный, «activate» не передаёт нужного контекста. Вот бы было можно заменить описание действия, дав больше контекста. И — сюрприз — так можно!
Каждая вью содержит сущность AccessibilityDelegate . Согласно документации, делегат позволяет делать компоненты доступными не с помощью наследования, а с помощью композиции. Он может перехватывать события с UI, создавать кастомные экшны, редактировать существующие — и ещё много вещей, которые прокачают доступность компонентов.
Давайте создадим делегата для нашей карточки. Будем использовать класс ViewCompat для обратной совместимости. Всё это опишем в методе setupAccessibility , который также вызываем при инициализации компонента.
ViewCompat.setAccessibilityDelegate( binding.root, object : AccessibilityDelegateCompat() < override fun onInitializeAccessibilityNodeInfo( host: View, info: AccessibilityNodeInfoCompat ) < super.onInitializeAccessibilityNodeInfo(host, info) val customAction = AccessibilityNodeInfoCompat.AccessibilityActionCompat( AccessibilityNodeInfoCompat.ACTION_CLICK, // Строка со значением "View wallet details" context.getString(R.string.wallet_card_accessibility_card_click_action) ) info.addAction(customAction) >> )
Вы великолепны. Теперь TalkBack при наведении на карточку зачитает действие как «Double tap to view wallet details».
Разберёмся с функциональностью, связанной с адресом. Для компонента помимо основных действий — нажатия, долгого нажатия и так далее — можно добавлять кастомные действия и вызывать их через меню TalkBack. Чтобы открыть меню, нужно при включенном TalkBack сделать L-образный свайп сверху вниз или свайп тремя пальцами вниз — если версия TalkBack выше 9.1. Для добавления кастомного действия используются:
- Функция ViewCompat.addAccessibilityAction , куда передаём корневую вью и название действия.
- Класс AccessibilityViewCommand , в которое кладём код с действием, — просто лямбда.
private fun setupAccessibility() < . . . ViewCompat.addAccessibilityAction( binding.root, // Строка "Show address QR code" context.getString(R.string.wallet_card_accessibility_show_qr_code_action) ) < _, _ ->// Открываем диалог с QR здесь Toast.makeText(context, "QR code has shown", Toast.LENGTH_SHORT).show() true > >
По такой же логике добавляем второе действие — копирование адреса кошелька.
private fun setupAccessibility() < . . . ViewCompat.addAccessibilityAction( binding.root, // Строка "Copy the wallet address" context.getString(R.string.wallet_card_accessibility_copy_the_address_action) ) < _, _ ->// Кладём строку с адресом в буфер обмена Toast.makeText(context, "Address has copied", Toast.LENGTH_SHORT).show() true > >
Кастомная вью теперь гораздо доступнее, чем была до этого. Вот так это будет выглядеть со стороны пользователя.
Можно и дальше придумывать, как сделать этот компонент более доступным. Я разобрал базовые случаи и инструменты для работы с Accessibility. Например, ещё придумал, что можно добавить кастомный экшн на чтение адреса полностью или первых и последних пяти символов.
Если получается много однотипного кода, можно вынести повторяющиеся вещи в функции-расширения. Кастомные действия также подходят для более сложных случаев взаимодействия с UI: перетаскивание элементов в списке, swipe-to-delete и прочие.
Возьмём другой пример. Пойдём дальше по тематике приложения с криптовалютами. У нас есть такой вот глупенький кастомный свитч, у которого есть 2 состояния: пополнить или перевести. Внутри это просто LinearLayout с двумя кнопками.

По смыслу это один компонент, но если ничего не делать, TalkBack будет читать эти две кнопки как отдельные компоненты. Чтобы сделать свитчер более доступным, нужно следующее:
1. Дать осмысленное название свитчу с помощью параметра contentDescription . В этот раз мы можем сделать это прямо в XML, потому что контент вью неизменный, в отличие от прошлой вью — там у нас на входе были название кошелька и баланс.
2. Все кнопки пометить importantForAccessibility = no , чтобы TalkBack не выделял их.
3. У этого компонента два состояния: пополнить или перевести. По смыслу эта кастомная вью — свитчер, но TalkBack про это ничего не знает. Чтобы добавить состояние для компонента, есть поле stateDescription . А зачем оно нужно? Мы же просто можем класть состояние в contentDescription и эффект будет тот же.

Каждый раз, когда мы обновляем состояние компонента и, следовательно, обновляем contentDescription , TalkBack будет его зачитывать.
В contentDescription , помимо состояния, есть ещё описание самого компонента. Чтобы его не повторять, а воспроизводить только изменённое состояние, это состояние выделили в отдельное поле. Хорошая практика: в contentDescription класть только описание компонента — «Operation type». Состояние класть в stateDescription — «Deposit» или «Transfer». Давайте обновлять у нашего свитчера состояние при изменении:
depositButton.setOnClickListener < depositButton.select() transferButton.deselect() updateStateDescription() >transferButton.setOnClickListener
private fun updateStateDescription() < ViewCompat.setStateDescription( binding.root, context.getString( if (binding.transferButton.isSelected) < R.string.operation_switch_transfer_label >else < R.string.operation_switch_deposit_label >) ) >
4. Помимо состояния, вью обозначает, что за компонент и какую роль он играет на экране: кнопки, переключателя, чекбокса, меню-бара и так далее. Можно просто закинуть эту роль в contentDescription — но, конечно, это плохая практика.

Есть два способа обозначить роли: указать className или использовать поле roleDescription . В чём их отличие? Поле className следует перегружать, если кастомный компонент имеет роль стандартного компонента: Button, Checkbox, RadioButton — просто его вёрстка и реализация отличаются.
Если компонент уникальный и у него нет аналогов в библиотеке стандартных компонентов, можно явно указать его название его название с помощью параметра roleDescription . Такими компонентами могут быть рекламные баннеры или кастомные меню-панели. В нашем случае мы создаём аналог Switch, поэтому будем использовать установку роли через className .
ViewCompat.setAccessibilityDelegate( root, object : AccessibilityDelegateCompat() < override fun onInitializeAccessibilityNodeInfo( host: View, info: AccessibilityNodeInfoCompat ) < super.onInitializeAccessibilityNodeInfo(host, info) info.className = Switch::class.java.name // Если вам нужно указать отличную от стандартной роль info.roleDescription = context.getString(R.string.role_description_switch) >> )
5. Мы хотим, чтобы при двойном нажатии на вью при включенном TalkBack свитчер менял своё значение. Этого мы можем добиться с помощью функции ViewCompat.replaceAccessibilityAction . Она переопределяет поведение переданного в функцию экшна, в нашем случае ACTION_CLICK на поведение, переданное в лямбде.
ViewCompat.replaceAccessibilityAction( root, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, // Строка "Toggle" context.getString(R.string.operation_switch_accessibility_toggle_action) ) < _, _ ->if (transferButton.isSelected) < depositButton.performClick() >else < transferButton.performClick() >true >
Всё! Что у нас получилось в итоге, можно посмотреть на видео ниже. Все примеры кода лежат у меня на GitHub.
Accessibility проще, чем кажется
- Всё не так сложно, как казалось на первый взгляд. Не нужно создавать отдельную версию приложения для людей с ограниченными возможностями.
- Стоит чаще запускать Accessibility Scanner и исправлять ошибки. Не забываем про указание заголовков для удобной навигации, не кладём null в contentDescription , следим за порядком навигации между компонентами и делаем кастомные вью доступными!
- Все строки, связанные с a11y, следует выносить в strings.xml и делать для них локализацию для языков, которые поддерживаются приложением.
- Все стандартные компоненты Android SDK по умолчанию поддерживают a11y.
- Во время тестирования приложения попробуйте попользоваться им, не подглядывая на экран, — только через TalkBack. Может быть, вы найдете моменты, которые можно было бы сделать лучше.
В поддержке Accessibility одни плюсы: люди с ограниченными возможностями смогут пользоваться приложениями без неудобств, в то же время в большинстве случаев у разработчиков поддержка не занимает много времени и ресурсов. Правда, порой встречаются очень необычные кейсы, где поддержать a11y — это вызов для всей команды разработки. Примерами могут послужить сложные кастомные вью, нарисованные на Canvas, большие таблицы с огромным количеством данных или графики.
Больше полезного про Android — в нашем телеграм-канале Surf Android Team. Здесь мы публикуем кейсы, лучшие практики, новости и вакансии Surf, а также проводим прямые эфиры. Присоединяйтесь!
Больше кейсов команды Surf ищите на сайте >>
- surf
- accessibility
- доступность
- мобильная разработка
- разработка под андроид
- разработка под android
- talkback
- разработка мобильных приложений
Как программно включить/отключить службу специальных возможностей в Android
Я хотел бы программно включить/отключить службы Accessibility, перечисленные в разделе Настройки->Доступность.
Я мог бы запустить Intent Accessibility следующим образом:
Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivityForResult(intent, 0);
Но я не имею никакого представления о том, как включить службы, перечисленные в представлении, через мой код.
Пожалуйста, предоставьте мне свои представления.
Поделиться Источник 08 апреля 2012 в 07:28
11 ответов
Я нашел решение, которое сработало для меня, вызвав
Settings.Secure.putString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "pkgname/classname"); Settings.Secure.putString(getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, "1");
Где pkgname — это имя вашего пакета, а classname — это имя класса вашей службы доступности.
Если вам нужно включить несколько служб или вы не хотите уничтожать предыдущие настройки, вы можете использовать : для разделения других служб.
Также вам может понадобиться запустить как системное приложение, и вам могут понадобиться следующие разрешения
Однако, согласно @Rupert Rawnsley, это может не работать на некоторых версиях Android, я работаю на Android 4.0.4 и надеюсь, что это сработает для вас.
PS. Если это не сработает, возможно, вы найдете удачу в /data/data/com.android.providers.settings/databases/settings.db/secure ,, где Android хранит защищенные настройки.
Поделиться 29 января 2015 в 08:24
Начиная с Android 6.0, вы можете использовать:
adb shell settings put secure enabled_accessibility_services packagname/servicename
Настройки settings.db из старых релизов больше не присутствуют на Android 6.0.
Поделиться 14 августа 2016 в 05:01
В Android 7 (API 24), AccessibilityService может программно отключиться, вызвав метод disableSelf().
Поделиться 07 ноября 2016 в 09:10
AccessibilityService является специальным и не может быть запущен программно.
Поделиться 24 мая 2012 в 15:33
Лучшее, что вы можете сделать, это вручную открыть настройки доступности с помощью:
Интент intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
и запустить интент — вы также можете сделать это из предпочтительного XML-файла:
Поделиться 03 декабря 2014 в 12:24
В инструментальных тестах я запускаю свой сервис доступности вот так:
private fun enableAccessibilityService() < val packageName = "com.example" val className = "$packageName.service.MyService" val string = "enabled_accessibility_services" val cmd = "settings put secure $string $packageName/$className" InstrumentationRegistry.getInstrumentation() .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) .executeShellCommand(cmd) .close() TimeUnit.SECONDS.sleep(3) >
Я тестировал на Android 6 и 8. Это также работает для несистемных приложений.
Поделиться 03 августа 2018 в 12:34
Вот рабочее решение (если ваша активность не запущена, оно отключится)
@Override protected void onServiceConnected() < super.onServiceConnected(); Toast.makeText(this, "Connected!",Toast.LENGTH_SHORT).show(); ScheduledExecutorService scheduleTaskExecutor = Executors.newScheduledThreadPool(1); scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() < public void run() < serviceChecker(); >>, 0, 5, TimeUnit.SECONDS); > private void serviceChecker()< if(!isActivityRunning(MainActivity.class)) < if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) < disableSelf(); >> > protected Boolean isActivityRunning(Class activityClass) < ActivityManager activityManager = (ActivityManager) getBaseContext().getSystemService(Context.ACTIVITY_SERVICE); Listtasks = activityManager.getRunningTasks(Integer.MAX_VALUE); for (ActivityManager.RunningTaskInfo task : tasks) < if (activityClass.getCanonicalName().equalsIgnoreCase(task.baseActivity.getClassName())) return true; >return false; >
Поделиться 24 марта 2019 в 03:23
На всякий случай, если кто-то все еще хочет отключить обратный разговор из adb, когда вы застряли в экране блокировки, вводя пароль или закрепление. Одна вещь, которую вы можете попробовать, это adb shell am force-stop com.google.android.marvin.talkback
Поделиться 20 марта 2017 в 23:18
Вы можете включить службы доступности для нескольких приложений, разделив их двоеточием, например, так:
adb shell settings put secure enabled_accessibility_services com.app1/com.app1.MyAccessibilityService:com.app2/com.app2.MyAccessibilityService
Поделиться 09 апреля 2021 в 19:34
enabled_accessibility_services содержит все включенные службы, разделенные на ‘:’. Это что-то вроде ‘serviceA:serviceB:serviceC’
Если вы замените строку, она включит ваш сервис, но также отключит все остальные.
Правильным кодом будет:
fun enableAccessibility(activity: Activity) < var accessibilityServices: String? = Settings.Secure.getString( activity.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES ) if (accessibilityServices == null) < accessibilityServices = "" >else if (accessibilityServices.isNotEmpty()) < accessibilityServices = ":$accessibilityServices" >Settings.Secure.putString( activity.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "$/$$accessibilityServices" ) Settings.Secure.putString( activity.contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED, "1" ) >
Вам нужно только объявить это разрешение:
Вы можете включить это разрешение только если вы корневые или через adb.
Поделиться 21 ноября 2021 в 05:46
Я нашел этот пост: Как программно проверить, объявлен ли сервис в AndroidManifest.xml? . Верхний ответ говорит о PackageManager, который сообщает вам, что запущено.
