Содержание статьи
Любой, кто писал более или менее серьезный софт для разных мобильных ОС, знает, что Android — самая открытая для разработчика ОС. Доступный для сторонних приложений API здесь гораздо шире, сама система гибче, а правила размещения приложений в маркете очень либеральные. Однако и в Android есть ряд системных API, скрытых от сторонних приложений и доступных только стоковому софту. В этой статье мы попробуем разобраться, как получить доступ к этим API и какие возможности они открывают.
Немного теории
Как мы все знаем, в Android есть такое понятие — полномочия приложений (permissions, разрешения). Полномочия прописываются в файл Manifest.xml каждого приложения и определяют то, к каким функциям API сможет получить доступ приложение. Хочешь работать с камерой — добавь в Manifest.xml
строку <uses-permission android:name="android.permission.CAMERA" />
. Нужен доступ к карте памяти — android.permission.READ_EXTERNAL_STORAGE
. Все просто и логично, к тому же все доступные приложениям полномочия хорошо документированы.
Есть, однако, в этой стройной схеме одна очень важная деталь, которую сами создатели Android называют уровень доступа (protection level). Чтобы понять, что это такое, попробуй добавить в Manifest.xml
любого своего приложения следующую строку:
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
По идее, данное полномочие должно открыть доступ к API, позволяющему переводить смартфон в режим полета, включать/выключать GPS и делать другие полезные вещи. Но IDE так не считает и поэтому сразу подчеркивает строку как ошибку с формулировкой «Permission is only granted to system apps». Это и есть предупреждение о нарушении того самого уровня доступа. IDE как бы говорит нам: да, ты можешь попробовать дать своему приложению полномочие WRITE_SECURE_SETTINGS
, но Android все равно не разрешит тебе использовать закрепленный за ним API до тех пор, пока ты не сделаешь свое приложение системным. А что значит «системным» в данном случае? Это значит: подпишешь его тем же цифровым ключом, каким подписана сама прошивка (иди попробуй раздобыть такой ключ у какой-нибудь Samsung или LG!).
Официально в Android существует четыре уровня доступа:
- normal — «обычные» полномочия, дающие приложению доступ к безобидным функциям, которые не получится зловредно использовать (примеры: SET_ALARM, ACCESS_NETWORK_STATE, VIBRATE). Система даже не скажет тебе, что приложение вообще их использует;
- dangerous — «опасные» полномочия, юзер будет информирован о них при установке приложения либо увидит окошко с предупреждением в Android 6.0 (примеры: READ_SMS, SEND_SMS, CALL_PHONE, READ_CALL_LOG);
- signature — доступны только приложениям, подписанным ключом прошивки (примеры: GET_TASKS, MANAGE_USERS, WRITE_SETTINGS, MOUNT_UNMOUNT_FILESYSTEMS);
- privileged — доступны приложениям, располагающимся в каталоге
/system/priv-app
.
В большинстве случаев уровни доступа signature и privileged равноценны. Например, чтобы получить полномочие MANAGE_USERS, приложение должно быть либо подписано ключом прошивки, либо размещено в каталоге /system/priv-app
. Но есть и исключения: например, полномочие MANAGE_DEVICE_ADMIN имеет уровень доступа signature, то есть единственный способ его получить — подписать приложение ключом прошивки.
Есть также набор внутренних уровней доступа, введенных в Android для решения определенных проблем: installer, development, preinstalled, appop, pre23. По сути, это костыли, и на данном этапе ты можешь о них не думать, однако к уровню доступа development мы еще вернемся, и он нам очень сильно пригодится. А пока поговорим о том, как получить нужные нам уровни доступа и что они дают.
Уровень доступа privileged
Privileged не самый высокий уровень доступа и позволяет использовать далеко не весь API Android. Однако в большинстве случаев он оказывается вполне достаточным, так как позволяет устанавливать и удалять приложения и пользователей (INSTALL_PACKAGES, DELETE_PACKAGES, MANAGE_USERS), управлять статусной строкой (STATUS_BAR), управлять некоторыми настройками питания (WRITE_SECURE_SETTINGS), читать и изменять настройки Wi-Fi (READ_WIFI_CREDENTIAL, OVERRIDE_WIFI_CONFIG), отключать приложения и их компоненты (CHANGE_COMPONENT_ENABLED_STATE) и многое другое.
Чтобы приложение получило уровень доступа privileged, оно должно быть установлено в каталог /system/priv-app
, а это значит — поставляться предустановленным в составе прошивки. Однако, имея root, мы можем поместить свое приложение в данный каталог с помощью двух функций:
// Подсобная функция, которая просто выполняет shell-команду
static public boolean runCommandWait(String cmd, boolean needsu) {
try {
String su = "sh";
if (needsu) { su = "su"; }
Process process = Runtime.getRuntime().exec(new String[]{su, "-c", cmd});
int result = process.waitFor();
return (result == 0);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// Функция делает указанное приложение системным и отправляет смартфон в мягкую перезагрузку
static public void makeAppSystem(String appName) {
String systemPrivAppDir = "/system/priv-app/";
String systemAppDir = "/system/app/";
String appPath = "/data/app/" + appName;
// Подключаем /system в режиме чтения-записи
if (!runCommandWait("mount -o remount,rw /system", true)) {
Log.e(TAG, "makeAppSystem: Can't mount /system");
return;
}
int api = Build.VERSION.SDK_INT;
String appDir = systemPrivAppDir;
// Копируем приложение в /system/priv-app или /system/app в зависимости от версии Android
if (api >= 21) {
runCommandWait("cp -R " + appPath + "* " + appDir, true);
runCommandWait("chown -R 0:0 " + appDir + appName + "*", true);
runCommandWait("rm -Rf " + appPath + "*", true);
} else {
if (api < 20) { appDir = systemAppDir; }
runCommandWait("cp " + appPath + "* " + appDir, true);
runCommandWait("chown 0:0 " + appDir + appName + "*", true);
runCommandWait("rm -f " + appPath + "*", true);
}
// Отправляем смартфон в мягкую перезагрузку
Shell.runCommand("am restart", true);
}
Функцию runCommandWait я описывать не буду, она просто выполняет shell-команду и ждет ее завершения (подробнее читай в моей статье про написание приложений с правами root). Функция makeAppSystem, в свою очередь, принимает полное имя приложения (это то самое com.example.app, которое ты указываешь при создании нового проекта в Android Studio) и переносит его в /system/priv-app
или /system/app
, в зависимости от используемой версии Android. Код может показаться тебе несколько странным, на самом деле он абсолютно корректен и учитывает два фактора:
- до Android 4.4 (API Level: 20) каталога
/system/priv-app
не существовало и все системные приложения размещались в/system/app
; - начиная с Android 5.0 (API Level: 21) пакеты с приложениями не просто складируются в
/data/app
и/system/priv-app
, а размещаются внутри своих обособленных подкаталогов.
Как использовать этот код? Очень просто: ты определяешь в Manifest.xml своего приложения все privileged-полномочия, которые ему нужны, не обращая внимания на ошибки IDE. Затем в самое начало кода приложения вставляешь вызов makeAppSystem с именем самого приложения в качестве аргумента, компилируешь и запускаешь. После запуска приложение перемещает само себя в /system/priv-app
, перезагружает смартфон, и ему открываются все privileged API.
Список privileged-полномочий можно посмотреть в исходниках Android. Просто ищи по слову privileged. О том, как их использовать, — чуть позже.
Уровень доступа signature
Подпись ключом прошивки позволяет получить самый высокий уровень доступа к API — signature. Имеющее такой доступ приложение может делать практически все что угодно: манипулировать любыми настройками Android (WRITE_SETTINGS, WRITE_SECURE_SETTINGS), наделять приложения правами администратора (MANAGE_DEVICE_ADMINS), программно нажимать кнопки и вводить данные в любое окно (INJECT_EVENTS) и многое другое.
Получить такой уровень доступа на стоковой прошивке почти невозможно. Ни один производитель смартфонов не предоставит тебе ключ для подписи своих прошивок. Но если речь идет о кастомной прошивке, то все становится немного проще. Например, ночные сборки того же CyanogenMod (а я напомню, что пользователей у нее больше, чем юзеров всех версий Windows Phone, вместе взятых) подписываются тестовым ключом, а его особенность в том, что он есть в открытом доступе.
Но это еще не все, в CyanogenMod есть механизм безопасности, который, в отличие от чистого Android, позволяет получать уровень доступа signature не абсолютно всем приложениям, подписанным ключом прошивки, а только тем, что размещены в /system/priv-app
. Поэтому, чтобы получить уровень доступа signature в CyanogenMod (не в Cyanogen OS, я подчеркиваю), необходимо:
- Добавить в Manifest.xml приложения необходимые полномочия.
- Добавить в приложение вызов функции makeAppSystem(), описанной в предыдущем разделе.
- Подписать релизную версию приложения ключом platform из репозитория CyanogenMod.
Уровень доступа development
В Android есть специальный уровень доступа development, отличие которого заключается в том, что приложения получают его не по факту размещения в /system/priv-app
или использования цифровой подписи прошивки, а динамически. То есть система может дать такой уровень доступа любому приложению, а может и отозвать обратно. Но самое важное, что, имея права root, приложение может наделить себя таким уровнем доступа самостоятельно.
Чтобы это сделать, достаточно использовать примерно такой код:
runCommandWait("pm grant " + appName + " android.permission.WRITE_SECURE_SETTINGS", true);
В данном случае приложение appName получит полномочие WRITE_SECURE_SETTINGS вне зависимости от того, где оно размещено и каким ключом подписано. Круто? Вне сомнения, однако WRITE_SECURE_SETTINGS — фактически единственное полезное полномочие с уровнем доступа development. Остальные четырнадцать — это полномочия для отладки и тестирования (чтение логов, дампы памяти и так далее).
Как использовать системный API?
Основная проблема, с которой ты столкнешься при работе с системным API, — это полное (за небольшими исключениями) отсутствие документации. Ни в официальных руководствах, ни в неофициальных ты не найдешь почти никаких упоминаний об этом. Информацию придется собирать по крупицам, прошаривая сотни страниц форумов и читая тысячи страниц исходников Android. Однако хоть и небольшую, но отправную точку в виде парочки полезных примеров мы тебе дадим.
WRITE_SECURE_SETTINGS
Полномочие WRITE_SECURE_SETTINGS появилось в Android 4.2 для защиты некоторых критически важных настроек Android. Среди таких настроек: включение/выключение режима полета, управление настройками местоположения и передачи данных. Оно защищено сразу тремя уровнями доступа: signature, privileged и development. То есть ты можешь использовать любой из перечисленных выше способов получения уровня доступа, чтобы наделить свое приложение полномочием WRITE_SECURE_SETTINGS.
Как использовать открывшиеся возможности? Например, так:
// Читаем текущее значение настройки
boolean isEnabled = Settings.System.getInt(
getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, 0) == 1;
// Переключаем настройку
Settings.System.putInt(
getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, isEnabled ? 0 : 1);
// Отправляем интент для переключения
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", !isEnabled);
sendBroadcast(intent);
Это очень простой код, который тупо переключает смартфон в режим полета или наоборот в зависимости от текущего состояния. Все просто и лаконично.
INSTALL_PACKAGES
Как ясно из названия, полномочие INSTALL_PACKAGES позволяет «втихую» устанавливать в систему APK-пакеты. Использовать эту возможность могут либо подписанные ключом прошивки приложения (signature), либо установленные в /system/priv-app
. При этом даже не обязательно использовать Java API, достаточно вызвать консолью команду pm (Package Manager) с нужными параметрами:
runCommandWait("pm install " + apkPath, false);
После отработки команды пакет apkPath будет установлен в систему. Ты можешь возразить, что то же самое можно сделать и с правами root, и будешь прав: в данном случае достаточно изменить последний аргумент функции runCommandWait() на true. Однако стоит иметь в виду, что приложения с правами root, во-первых, приводят к появлению окна запроса соответствующих полномочий у юзера, а во-вторых, логируются тем же SuperSU. А так достаточно один раз прописать свою софтину в /system/priv-app
, и она сможет устанавливать сколько угодно софта без всяких вопросов.
Вместо выводов
Вот и все. Доступ к закрытому API в Android не так уж и сложно получить. С другой стороны, с легитимным софтом использовать его в большинстве случаев не имеет смысла, проще получить права root и вызывать соответствующие консольные команды: settings для изменения настроек, pm для установки/удаления приложений, setprop для изменения низкоуровневых настроек и так далее. Однако если речь идет о не совсем обычном программном обеспечении...