Как блокировать приложения на Android без AccessibilityService — альтернативный подход
Практически все блокировщики приложений в Google Play используют AccessibilityService, но это избыточный и подозрительный для пользователя инструмент. Разработчик приложения Takeover показал, как обойтись более щадящим подходом — и объяснил, где тут подводные камни.
AccessibilityService даёт приложению доступ к содержимому каждого экрана, включая банкинг. Для блокировщика это всё равно что вскрывать гаечным ключом замок, когда подходит обычная отмычка — принципиально возможно, но избыточно и подозрительно.
Зачем вообще отказываться от AccessibilityService
AccessibilityService — мощный инструмент Android, задуманный для помощи людям с ограниченными возможностями: экранные дикторы, альтернативные способы ввода, автоматизация. Разработчики блокировщики давно приспособили его для своих нужд: сервис получает колбэк при каждой смене окна, читает имя пакета и при необходимости перекрывает запрещённое приложение. Просто, эффективно, работает.
Проблема в том, что при установке такого приложения пользователь видит пугающий запрос: «Это приложение может видеть всё, что отображается на экране». В том числе — содержимое банковского приложения, переписок, паролей. С точки зрения Google, если задачу можно решить более узким API, нужно использовать именно его. Ревьюеры в Play Store действительно отклоняют блокировщики, запрашивающие AccessibilityService без крайней необходимости, а пользователи всё чаще отказываются давать это разрешение.
Альтернатива: UsageStatsManager + foreground service
Разработчик приложения Takeover (фокус-блокировщик) описывает подход, который он запустил в продакшене. Суть проста:
- Foreground service каждые ~900 мс опрашивает
UsageStatsManager, чтобы узнать, какое приложение сейчас на переднем плане. - Если это приложение из «чёрного списка», поверх него запускается полупрозрачная Activity — экран блокировки.
Всего три разрешения в манифесте:
PACKAGE_USAGE_STATS— доступ к статистике использованияSYSTEM_ALERT_WINDOW— отображение окна поверх других приложенийFOREGROUND_SERVICE+FOREGROUND_SERVICE_SPECIAL_USE— работа в фоне
Ключевой нюанс: PACKAGE_USAGE_STATS — не runtime-разрешение. Его нельзя запросить стандартным диалогом. Пользователь должен сам зайти в Настройки → Специальный доступ → Доступ к статистике использования и включить его. Разработчик честно признаёт: это серьёзный момент оттока при онбординге. Экран выглядит пугающе, расположен в нетривиальном месте настроек, и никакой текст его не сделает привычным.
Как правильно читать активное приложение
Основной цикл — foreground service с Handler, который вызывает проверку каждые 900 мс. Метод checkForeground() через queryEvents() получает события типа MOVE_TO_FOREGROUND и запоминает имя пакета.
Здесь автор выделяет две типичные ошибки, которые не найти в туториалах:
1. queryEvents возвращает события, а не состояние. Если пользователь сидит в одном приложении минуту, запрос вернёт пустоту — ведь ничего не изменилось. Это главный баг во всех подобных реализациях: разработчик сбрасывает состояние в «нет активного приложения», и блокировка мигает или пропадает. Решение — хранить последнее известное приложение в поле и обновлять только при реальном событии.
2. Используйте скользящее окно, а не фиксированное. Формат [lastQueryTime, now], где lastQueryTime сдвигается только после успешного чтения. Буфер событий не строго реалтаймный — некоторые события приходят с задержкой в сотни миллисекунд. Фиксированное окно [now - 1000, now] их потеряет. При старте сервиса нужно отступить на пару секунд назад, иначе пропустите переход, случившийся прямо перед началом мониторинга.
Экран блокировки
Обычная Activity, но с тремя настройками в манифесте:
- Полупрозрачная тема (
Theme.Translucent.NoTitleBar.Fullscreen) excludeFromRecents="true"— не появляется в списке недавних приложенийlaunchMode="singleInstance"— одна копия, не стакуется
Флаги запуска: FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP. Комбинация singleInstance + SINGLE_TOP предотвращает ситуацию, когда при открытии заблокированного приложения на экране «стробоскопом» мелькают шесть копий блокировки.
Три защитных механизма, без которых ничего не работает
Автор особо подчёркивает: ни один туториал этого не упоминает, но именно эти проверки отличают «работает на эмуляторе» от «работает на чужом телефоне».
Повторные события при запуске. Некоторые приложения генерируют несколько событий MOVE_TO_FOREGROUND при открытии. Без защиты блокировка запускается, потом приходит следующее событие, потом ещё — пользователь видит мигание. Решение: флаг @Volatile на Activity, показывающий, что блокировка уже видна, и пропуск повторного запуска для того же пакета в течение секунды.
Возврат в своё приложение — тоже переход. Когда пользователь нажимает «назад», заблокированное приложение на мгновение всплывает. Нужен маркер перехода с игнорированием этого пакета на ~3 секунды.
Реальный способ обойти блокировку. Если пользователь намеренно разблокировал приложение, нужно дать ему «льготный период» — 5 минут без блокировки для этого конкретного приложения. Блокировщик без лазейки не воспитывает дисциплину — его просто удаляют.
Тип foreground-сервиса на Android 14+
Начиная с Android 14 нужно явно указать тип foreground-сервиса. Блокировка приложений не попадает ни под один специфический тип, поэтому используется specialUse. В манифесте нужно добавить property с объяснением для ревьюера Google Play: мол, сервис обеспечивает блокировку приложений, выбранную пользователем, путём определения активного приложения через usage access.
Запуск — через startForegroundService, а в startForeground на API 34+ передаётся FOREGROUND_SERVICE_TYPE_SPECIAL_USE. Возвращаемое значение START_STICKY — чтобы система перезапустила мониторинг, если убьёт процесс во время сессии.
Цена подхода: что вы теряете
Автор честно перечисляет компромиссы:
-
Задержка. Опрос раз в 900 мс означает до секунды между моментом, когда пользователь открыл запрещённое приложение, и появлением блокировки. AccessibilityService срабатывает практически мгновенно. Для реального кейса это приемлемо — пользователь на секунду увидит ленту, а потом её не увидит.
-
Нетипичное разрешение. Пользователь должен сам найти нужный экран в настройках и включить переключатель. Никакого диалога с одной кнопкой.
-
Постоянное уведомление. Foreground service = значок в шторке. Без вариантов.
-
Производители ломают. Агрессивная экономия батареи на некоторых оболочках (MIUI, One UI, EMUI) убивает сервис.
START_STICKYпомогает, но не гарантирует.
Зато вы никогда не просили разрешения читать экран пользователя
Для блокировщика это правильный компромисс — и с точки зрения прохождения ревью в Play Store, и с точки зрения доверия пользователя.
Важная оговорка
Это не инструмент родительского контроля «военного уровня». Любой может зайти в Настройки и отозвать доступ к статистике — и блокировки как не бывало. Цель дизайна — не сделать обход невозможным, а сделать его усилием на ~30 секунд осознанных действий. Как показывает практика, этого обычно хватает: желание открыть Instagram длится меньше, чем путь до настроек.