Скрытый API: интерфейсы, которые Android прячет от разработчиков
В одной из своих предыдущих статей я уже писал о механизме под названием «уровень доступа» (protection level), который определяет, может ли твой код обращаться к тем или иным функциям ОС. Высокий уровень доступа получает только системный софт, поэтому для простых смертных он закрыт. Однако есть в Android и еще одна интересность, имя которой — скрытый API. И чтобы получить к нему доступ, не нужен root, не надо подписывать приложение ключом прошивки, достаточно лишь немного пораскинуть мозгами.
Intro
Написать эту статью подвигла одна история. Началась она с попытки создать простенькое приложение, которое позволяло бы разворачивать строку состояния свайпом с какой-то из сторон экрана. Современные смартфоны слишком велики, чтобы дотянуться до статусбара одной рукой, а свайп позволил бы решить эту проблему быстро и легко.
Подобная функция есть во многих лаунчерах, поэтому задача казалась простой и совершенно очевидной: завариваем кофе, открываем доки Android, находим нужную функцию и пишем простой сервис, который отслеживал бы касание края экрана и разворачивал статусбар.
Но жестокая реальность поубавила оптимизма: как следовало из документации Android, API не предоставлял такую функциональность! А значит, софт, умеющий разворачивать строку состояния, использовал хаки, а что еще более интересно — хаки, работающие без root, прав администратора и вообще каких бы то ни было разрешений.
Начинаем разбираться
Проще всего выяснить, как это вообще возможно, — посмотреть чужой код. Такого с ходу не нашлось, зато обнаружилась очень простая софтина Drop Down Status Bar. Она состояла из иконки, при нажатии которой разворачивался статусбар, а сам код приложения умещался в файле размером 1252 байт — идеальный кандидат для декомпиляции.
Оставалось только скачать APK и натравить на него jadx:
Декомпилированный листинг Drop Down Status Bar
Xakep #212. Секреты даркнетаОчень простой код, который создает объект класса StatusBarManager и вызывает его метод expandNotificationPanel() , если приложение работает в среде Android 4.2, или метод expand() , если это Android предыдущих версий. Все очень просто, и код можно было банально скопировать в свое приложение:
Но не тут-то было. Оказалось, что класс StatusBarManager не просто не был описан в документации, — его вообще не существовало в SDK. Как же работал Drop Down Status Bar?
На самом деле все элементарно. Фреймворк, содержащий все классы пакета android (включая требуемый android.app.StatusBarManager), не один и тот же на реальном устройстве и в SDK. Версия фреймворка в SDK, во-первых, довольно сильно урезана в плане доступных классов, а во-вторых, не включает в себя самого кода реализации классов (вместо методов и конструкторов — заглушки).
Содержимое фреймворков реального устройства и SDK
Это теория, а практика в том, что выдернутый с устройства фреймворк по логике можно было бы использовать не только чтобы сравнить с тем, что поставляется в SDK, но и чтобы подменить его! Сделать это оказалось несложно.
Кручу, верчу, запутать хочу
Фреймворк был выдернут с устройства (что такое adb shell):
С помощью dex2jar байт-код Dalvik был транслирован обратно в байт-код Java:
И затем размещен в проекте как обычная библиотека:
Оставалось только запустить Android Studio, выбрать библиотеку и присоединить ее к проекту с помощью меню «Add as library».
Но Android Studio продолжал упорствовать. Теперь ему не нравилось слово statusbar:
Оказалось, однако, что неправ в этой ситуации как раз Android Studio и это не что иное, как баг, обойти который можно с помощью комментария-директивы noinspection:
Заставляем Android Studio принять наш код
Вот и все. нет, стоп, это я выдаю желаемое за действительное. На самом деле это еще далеко не все. Из-за огромного веса фреймворка Android Studio задыхался во время компиляции и постоянно прерывал этот процесс с самыми разными ошибками. И ошибки эти были вовсе не в коде, а в самих инструментах сборки. И даже не ошибки, а расход всей оперативной памяти, из-за которого инструменты сборки просто падали, как, например, утилита dx, перегоняющая байт-код Java в байт-код Dalvik:
Решение этому нашлось не сразу, и поначалу казалось, что нечего даже пытаться собрать код на ноуте с четырьмя гигами памяти. Однако и это было возможно, но только если указать Android Studio альтернативный каталог для хранения временных файлов (по умолчанию в Linux он использует каталог /tmp , который зачастую сам находится в оперативке), подключить swap и провести небольшой тюнинг системы сборки.
Первые две задачи решились просто:
Вторая чуть сложнее. Пришлось слегка подредактировать build.gradle проекта, чтобы выделить побольше памяти виртуальной машине Java, отключить ProGuard и снять ограничение на 65 тысяч методов (multiDex):
Оставалось только дождаться окончания сборки.
И тут я подумал о рефлексии.
На самом деле все сказанное выше — пустая болтовня. Не потому, что этот метод не работает, — он замечательно работает, и ты сам можешь в этом убедиться. Настоящая причина в том, что он невероятно избыточен, ведь есть более адекватный альтернативный путь. Итак, внимание, код для вытягивания шторки без замен фреймворков и возни с настройками Java и Gradle:
Все просто. Достаточно было использовать рефлексию, чтобы прямо во время исполнения найти класс StatusBarManager, найти его метод expandNotificationsPanel() и вызвать. И все это без лишних телодвижений (кроме того, что после каждого редактирования код приходится запускать для проверки).
Какие еще скрытые API существуют?
На самом деле их не так уж много. В основном Android использует скрытые API для взаимодействия между системными классами, поэтому обычно это различные константы и подсобные функции, малоинтересные обычным программистам. Но есть и несколько полезных API, которые позволяют:
- монтировать, размонтировать и форматировать файловые системы (StorageManager);
- получить расширенную информацию о Wi-Fi (WifiManager);
- узнать UID и прочую информацию о текущем процессе (Process);
- получить расширенную информацию о базовой станции (CellInfoLte);
- узнать тип сети (ConnectivityManager);
- получить список установленных пакетов, принадлежащих указанному юзеру (PackageManager);
- узнать реальный размер экрана с учетом наэкранных кнопок навигации (Display).
Я не проверял все эти API, поэтому не буду давать гарантий, что они корректно работают и не требуют каких-то привилегий в системе. Ты можешь проверить сам — просто найди API в исходном коде, введя в поиске директиву @hide. Удобнее всего сделать это с помощью веб-сервиса AndroidXRef: просто укажи @hide в поле Full search, а в In project(s) выбери frameworks.
Ищем скрытые API в исходниках фреймворка
Мораль
Скрытые API и рефлексия позволили мне реализовать задуманное (если тебе интересно, это чудо есть в маркете). Однако это всего лишь маленькая софтинка, написанная для себя, и я настоятельно не рекомендую использовать скрытые API в больших проектах, особенно если ты собираешься их монетизировать.
В отличие от API с высоким уровнем доступа, наличие или неизменность скрытых API не гарантирована. В следующей версии Android они могут исчезнуть или измениться, они могут существовать в прошивках одних аппаратов и отсутствовать в других. Их использование — это всегда лотерея.
Евгений ЗобнинРедактор рубрики X-Mobile. По совместительству сисадмин. Большой фанат Linux, Plan 9, гаджетов и древних видеоигр.