В одной из своих предыдущих статей я уже писал о механизме под названием «уровень доступа» (protection level), который определяет, может ли твой код обращаться к тем или иным функциям ОС. Высокий уровень доступа получает только системный софт, поэтому для простых смертных он закрыт. Однако есть в Android и еще одна интересность, имя которой — скрытый API. И чтобы получить к нему доступ, не нужен root, не надо подписывать приложение ключом прошивки, достаточно лишь немного пораскинуть мозгами.

 

Intro

Написать эту статью подвигла одна история. Началась она с попытки создать простенькое приложение, которое позволяло бы разворачивать строку состояния свайпом с какой-то из сторон экрана. Современные смартфоны слишком велики, чтобы дотянуться до статусбара одной рукой, а свайп позволил бы решить эту проблему быстро и легко.

Подобная функция есть во многих лаунчерах, поэтому задача казалась простой и совершенно очевидной: завариваем кофе, открываем доки Android, находим нужную функцию и пишем простой сервис, который отслеживал бы касание края экрана и разворачивал статусбар.

Но жестокая реальность поубавила оптимизма: как следовало из документации Android, API не предоставлял такую функциональность! А значит, софт, умеющий разворачивать строку состояния, использовал хаки, а что еще более интересно — хаки, работающие без root, прав администратора и вообще каких бы то ни было разрешений.

 

Начинаем разбираться

Проще всего выяснить, как это вообще возможно, — посмотреть чужой код. Такого с ходу не нашлось, зато обнаружилась очень простая софтина Drop Down Status Bar. Она состояла из иконки, при нажатии которой разворачивался статусбар, а сам код приложения умещался в файле размером 1252 байт — идеальный кандидат для декомпиляции.

Оставалось только скачать APK и натравить на него jadx:

Декомпилированный листинг Drop Down Status Bar
Декомпилированный листинг Drop Down Status Bar

Очень простой код, который создает объект класса StatusBarManager и вызывает его метод expandNotificationPanel(), если приложение работает в среде Android 4.2, или метод expand(), если это Android предыдущих версий. Все очень просто, и код можно было банально скопировать в свое приложение:

Упс...
Упс...

Но не тут-то было. Оказалось, что класс StatusBarManager не просто не был описан в документации, — его вообще не существовало в SDK. Как же работал Drop Down Status Bar?

На самом деле все элементарно. Фреймворк, содержащий все классы пакета android (включая требуемый android.app.StatusBarManager), не один и тот же на реальном устройстве и в SDK. Версия фреймворка в SDK, во-первых, довольно сильно урезана в плане доступных классов, а во-вторых, не включает в себя самого кода реализации классов (вместо методов и конструкторов — заглушки).

Содержимое фреймворков реального устройства и SDK
Содержимое фреймворков реального устройства и SDK

Это теория, а практика в том, что выдернутый с устройства фреймворк по логике можно было бы использовать не только чтобы сравнить с тем, что поставляется в SDK, но и чтобы подменить его! Сделать это оказалось несложно.

 

Кручу, верчу, запутать хочу

Фреймворк был выдернут с устройства (что такое adb shell):

$ adb shell
> su
> cp /system/framework/framework.jar /sdcard/
> exit
> exit

$ adb pull /sdcard/framework.jar

С помощью dex2jar байт-код Dalvik был транслирован обратно в байт-код Java:

$ unzip framework.jar
$ dex2jar-2.0/d2j-dex2jar.sh classes.dex

И затем размещен в проекте как обычная библиотека:

$ cp classes-dex2jar.jar ~/AndroidstudioProjects/ИМЯ_ПРИЛОЖЕНИЯ/app/libs/

Оставалось только запустить Android Studio, выбрать библиотеку и присоединить ее к проекту с помощью меню «Add as library».

INFO


Получившийся classes-dex2jar.jar можно было бы переименовать в android.jar и положить его в SDK/platforms/android-23, заменив оригинал. Но это не самая лучшая идея, так как изменение отразилось бы на всех остальных проектах.

Но Android Studio продолжал упорствовать. Теперь ему не нравилось слово statusbar:

StatusBarManager statusBarManager = (StatusBarManager) context.getSystemService("statusbar");

Оказалось, однако, что неправ в этой ситуации как раз Android Studio и это не что иное, как баг, обойти который можно с помощью комментария-директивы noinspection:

//noinspection ResourceType
Заставляем Android Studio принять наш код
Заставляем Android Studio принять наш код

Вот и все... нет, стоп, это я выдаю желаемое за действительное. На самом деле это еще далеко не все. Из-за огромного веса фреймворка Android Studio задыхался во время компиляции и постоянно прерывал этот процесс с самыми разными ошибками. И ошибки эти были вовсе не в коде, а в самих инструментах сборки. И даже не ошибки, а расход всей оперативной памяти, из-за которого инструменты сборки просто падали, как, например, утилита dx, перегоняющая байт-код Java в байт-код Dalvik:

Error:Execution failed for task ':app:transformClassesWithDexForDebug'.

Решение этому нашлось не сразу, и поначалу казалось, что нечего даже пытаться собрать код на ноуте с четырьмя гигами памяти. Однако и это было возможно, но только если указать Android Studio альтернативный каталог для хранения временных файлов (по умолчанию в Linux он использует каталог /tmp, который зачастую сам находится в оперативке), подключить swap и провести небольшой тюнинг системы сборки.

Первые две задачи решились просто:

$ export _JAVA_OPTIONS="-Djava.io.tmpdir=$HOME/tmp"
$ dd if=/dev/zero of=swap.img bs=1m count=4096
$ mkswap swap.img
$ sudo swapon swap.img

Вторая чуть сложнее. Пришлось слегка подредактировать build.gradle проекта, чтобы выделить побольше памяти виртуальной машине Java, отключить ProGuard и снять ограничение на 65 тысяч методов (multiDex):

android {
    ...
    defaultConfig {
        multiDexEnabled true
    }
    dexOptions {
        javaMaxHeapSize "4g"
    }
    ...
    buildTypes {
        debug {
            minifyEnabled false
        }
        release {
            minifyEnabled false
        }
    }
}

Оставалось только дождаться окончания сборки.

 

И тут я подумал о рефлексии...

На самом деле все сказанное выше — пустая болтовня. Не потому, что этот метод не работает, — он замечательно работает, и ты сам можешь в этом убедиться. Настоящая причина в том, что он невероятно избыточен, ведь есть более адекватный альтернативный путь. Итак, внимание, код для вытягивания шторки без замен фреймворков и возни с настройками Java и Gradle:

try {
    // noinspection ResourceType
    Object service = context.getSystemService("statusbar");
    Class<?> statusbarManager = Class.forName("android.app.StatusBarManager");
    Method expand = statusbarManager.getMethod("expandNotificationsPanel");
    expand.invoke(service);
} catch (Exception e) {
    Log.e("StatusBar", e.toString());
}

Все просто. Достаточно было использовать рефлексию, чтобы прямо во время исполнения найти класс 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 с высоким уровнем доступа, наличие или неизменность скрытых API не гарантирована. В следующей версии Android они могут исчезнуть или измениться, они могут существовать в прошивках одних аппаратов и отсутствовать в других. Их использование — это всегда лотерея.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии