Как повысить разрешения приложений с помощью библиотеки Shizuku

Рутирование доступно не всем. Вот как вы можете получить повышенные разрешения на уровне оболочки в своем приложении с помощью библиотеки Shizuku.

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

Shizuku позволяет вызывать системные API практически напрямую и полностью на Java или Kotlin. Это руководство покажет вам, как реализовать и использовать Shizuku.

Что такое Шизуку?

Прежде чем мы приступим к использованию Шизуку, возможно, будет полезно узнать, что именно это такое. Если вы знакомы с Магиск, то Шизуку аналогична. Но вместо управления корневым доступом он управляет доступом к оболочке.

Shizuku запускает собственный процесс с разрешениями на уровне оболочки. То, как пользователь активирует этот процесс, зависит от его устройства, версии Android и выбора. Shizuku можно активировать через ADB, через беспроводную сеть ADB на устройстве (

на Android 11 и более поздних версиях), или через root-доступ. Приложения, реализующие Shizuku, могут затем запросить разрешение на использование этого процесса для выполнения операций с повышенными правами.

СидзукуРазработчик: Синчен и Рикка

Цена: Бесплатно.

4.1.

Скачать

Почему Шизуку?

Хотя доступ к системе на уровне оболочки не позволяет вам делать столько, сколько корень, оно по-прежнему дает вам больше доступа, чем обычное приложение. Кроме того, способ работы Shizuku позволяет вам использовать API-интерфейсы Android почти как обычно. Вам не обязательно полагаться на команды оболочки (хотя вы можете, если хотите).

Если вашему приложению требуются специальные разрешения, которые можно предоставить только через ADB (или с помощью root), Shizuku и Android 11 станут отличным сочетанием. Вы можете просто использовать Shizuku для предоставления специальных разрешений прямо на устройстве.

Shizuku может быть полезен даже на устройствах, не работающих на Android 11. Приложение предоставляет пользователям инструкции и сценарии, поэтому вам не придется это делать.

Интеграция

Добавить Шизуку в ваше приложение не так просто, но и несложно. К сожалению, документация разработчика не совсем полный, но эта статья расскажет вам. Вот как можно интегрировать Shizuku в ваше приложение.

Зависимости

Первым шагом является добавление зависимостей Shizuku. В build.gradle на уровне модуля добавьте следующее в блок зависимостей.

def shizuku_version = '11.0.3'

implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"

Обязательно обновите версию, если это необходимо. 11.0.3 — последняя на момент написания.

Поставщик

Чтобы Shizuku работал, вам необходимо добавить блок провайдера в манифест вашего приложения. Откройте AndroidManifest.xml и добавьте следующее в блок приложения.

 android: name="rikka.shizuku.ShizukuProvider"
android: authorities="${applicationId}.shizuku"
android: multiprocess="false"
android: enabled="true"
android: exported="true"
android: permission="android.permission.INTERACT_ACROSS_USERS_FULL" />

Разрешение

Для авторизации Шизуку использует разрешение времени выполнения. Мы займемся предоставлением этого разрешения немного позже. А пока добавьте его в AndroidManifest.xml внутри блока манифеста.

Теперь, когда все это добавлено, базовая интеграция завершена. Позвольте Gradle выполнить синхронизацию проекта и перейдите к использованию.


Применение

Проверка доступности

Прежде чем перейти к использованию Шизуку, давайте поговорим о том, чтобы убедиться, что он действительно доступен для использования.

Прежде чем проверять, предоставлено ли разрешение, и перед выполнением вызовов API через Shizuku, вы можете убедиться, что эти проверки и вызовы пройдут успешно, с помощью следующего метода:

Shizuku.pingBinder()

Если Shizuku установлен и запущен, это вернется истинный. В противном случае он вернет false.

Предоставление разрешения

Поскольку Shizuku использует разрешение времени выполнения, оно должно быть предоставлено вашему приложению, прежде чем вы сможете что-либо делать с доступом к оболочке. Также в обращении находятся две версии API с разными способами предоставления. Этот раздел покажет вам, как справиться с обоими.

Проверка

Прежде чем запрашивать разрешение, лучше всего проверить, есть ли оно у вас уже. Если да, то вы можете продолжать делать все, что вам нужно. В противном случае вам придется запросить его, прежде чем продолжить.

Чтобы проверить, есть ли у вас разрешение на использование Shizuku, вы можете использовать следующее. Этот код предполагает, что вы запускаете его внутри Activity.

Котлин:

val isGranted = if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED
} else {
Shizuku.checkSelfPermission() = PackageManager.PERMISSION_GRANTED
}

Джава:

boolean isGranted;
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
isGranted = checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED;
} else {
isGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED;
}

Запрос

Если вам нужно запросить разрешение на использование Сидзуку, вот как это сделать.

 ШИЗУКУ_КОД переменная, используемая ниже, должна быть целым числом с постоянным значением (статическая переменная).

Котлин:

if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(arrayOf(ShizukuProvider.PERMISSION), SHIZUKU_CODE)
} else {
Shizuku.requestPermission(SHIZUKU_CODE)
}

Джава:

if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(newString[] { ShizukuProvider.PERMISSION }, SHIZUKU_CODE);
} else {
Shizuku.requestPermissions(SHIZUKU_CODE);
}

Чтобы прослушать результат, вам необходимо переопределить Activity onRequestPermissionsResult() метод. Вам также потребуется реализовать Шизуку. Слушатель OnRequestPermissionResultListener. Пример, который я собираюсь показать, предполагает, что ваша Activity реализует этот интерфейс, но вы можете реализовать его в переменной, если хотите.

Котлин:

classExampleActivity : AppCompatActivity, Shizuku.OnRequestPermissionResultListener {
...

override void onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
permissions.forEachIndexed { index, permission ->
if (permission == ShizukuProvider.PERMISSION) {
onRequestPermissionResult(requestCode, grantResults[index])
}
}
}

override voidonRequestPermissionResult(requestCode: Int, grantResult: Int){
val isGranted = grantResult == PackageManager.PERMISSION_GRANTED
//Dostuff based on the result.

}
}

Джава:

publicclassExampleActivityextendsAppCompatActivityimplementsShizuku.OnRequestPermissionResultListener{
...

@Override
publicvoidonRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int result = grantResults[i];

if (permission.equals(ShizukuProvider.PERMISSION) {
onRequestPermissionResult(requestCode, result);
}
}
}

@Override
publicvoidonRequestPermissionResult(int requestCode, int grantResult){
boolean isGranted = grantResult == PackageManager.PERMISSION_GRANTED;
//Dostuff based on the result.
}
}

Использование API

Теперь, когда Shizuku настроен и разрешения предоставлены, вы можете начать вызывать API с помощью Shizuku. Процесс здесь немного отличается от того, к которому вы привыкли. Вместо того, чтобы звонить getSystemService() и приведение к чему-то вроде WindowManager, вместо этого вам придется использовать внутренние API (например, IWindowManager).

Shizuku включает в себя обход скрытого черного списка API Android, поэтому вам не придется об этом беспокоиться при его использовании. Если вам интересно, как обойти это самостоятельно, ознакомьтесь с моим предыдущим руководством.

Вот краткий пример (с использованием отражения), показывающий, как можно получить экземпляр IPackageManager и используйте его, чтобы предоставить приложению разрешение во время выполнения.

Котлин:

val iPmClass = Class.forName("android.content.pm.IPackageManager")
val iPmStub = Class.forName("android.content.pm.IPackageManager\$Stub")
val asInterfaceMethod = iPmStub.getMethod("asInterface", IBinder::class.java)
val grantRuntimePermissionMethod = iPmClass.getMethod("grantRuntimePermission", String:: class.java /* package name */, String:: class.java /* permission name */, Int:: class.java /* user ID */)

val iPmInstance = asInterfaceMethod.invoke(null, ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")))

grantRuntimePermissionMethod.invoke(iPmInstance, "com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)

Джава:

Class< ?>> iPmClass = Class.forName("android.content.pm.IPackageManager");
Class< ?>> iPmStub = Class.forName("android.content.pm.IPackageManager$Stub");
Method asInterfaceMethod = iPmStub.getMethod("asInterface", IBinder.class);
Method grantRuntimePermissionMethod = iPmClass.getMethod("grantRuntimePermission", String.class, String.class, int.class);

val iPmInstance = asInterfaceMethod.invoke(null, new ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")));

grantRuntimePermissionMethod.invoke(iPmInstance, "com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);

Процесс для других API аналогичен. Получите ссылки на класс и его подкласс Stub. Получите ссылку на asInterface метод для класса Stub. Получите ссылки на нужные методы из самого класса. Затем вызовите asInterface ссылка на метод, которая у вас есть, заменив "package" выше с любой услугой, которая вам нужна. Затем этот экземпляр можно передать для вызовов методов.

Совет: вы можете вообще избежать использования отражения, если установите модифицированный SDK. Посетите репозиторий anggrayudi на GitHub. модифицированные SDK и инструкции по установке. После его установки приведенный выше код (на Kotlin) становится намного проще.

val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)

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

Обслуживание пользователей

Хотя метод Binder охватывает большинство случаев использования, могут быть случаи, когда вам понадобится API, не имеющий прямого интерфейса Binder. В этом случае вы можете использовать функцию обслуживания пользователей в Shizuku.

Этот метод работает аналогично обычному Сервису в Android. Вы «запускаете» его, связываетесь с ним путем привязки к нему с помощью ServiceConnection и запускаете свою логику в классе обслуживания. Разница в том, что вы не используете службу Android, и все внутри службы работает с разрешениями ADB.

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

Для этого также требуется Shizuku версии 10 или новее. Хотя большинство источников приложения в настоящее время имеют версию 11, вам все равно следует включить проверку версии, которая будет включена в пример.

Определение AIDL

Для начала вам нужно создать новый файл AIDL. Вы можете сделать это в Android Studio, щелкнув правой кнопкой мыши что-либо в представлении файлов Android, наведя указатель мыши на параметр «Новый» и выбрав параметр «AIDL». Введите имя файла (например, «IUserService»), и Android Studio создаст для вас файл шаблона.

Если вы не знакомы с тем, как работают AIDL, обязательно ознакомьтесь документация Google.

Удалите методы шаблона из AIDL, а затем добавьте destroy() метод с правильным идентификатором Шизуку. Это будет вызвано, когда Шизуку уничтожит службу пользователя, и его следует использовать для очистки любых имеющихся у вас ссылок или текущей логики.

Пример AIDL:

packagetk.zwander.shizukudemo;

interfaceIUserService{
voiddestroy()= 16777114;

void grantPermission(String packageName, String permission, int userId) = 1; //You can specify your own method IDs in the AIDL. Check out the documentation for more details on this.
}

Реализация AIDL

Следующее, что нужно сделать, это реализовать AIDL.

Джава:

publicclassUserServiceextendsIUserService.Stub{
//Make sure to include an empty constructor.
publicUserService(){
}

@Override
publicvoiddestroy(){
//Shizuku wants the service to be killed. Clean up and then exit.
System.exit(0);
}

@Override
publicvoidgrantPermission(String packageName, String permission, int userId){
//No need to use ShizukuBinderWrapper.
IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package")).grantRuntimePermission(packageName, permission, userId);
}
}

Котлин:

classUserService : IUserService.Stub {
//Include an empty constructor.
constructor() {
}

override fun destroy(){
//Shizuku wants the service to be killed. Clean up and exit.
System.exit(0)
}

override fun grantPermission(packageName: String, permission: String, userId: Int) {
//No need for ShizukuBinderWrapper.
IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package")).grantRuntimePermission(packageName, permission, userId)
}
}

В приведенных выше примерах предполагается, что у вас есть Скрытый API Android SDK установлен.

Настройка подключения к сервису

Теперь, когда пользовательская служба определена и реализована, пришло время настроить ее для использования. Первое, что вам нужно сделать, это определить ServiceConnection, с которым вы хотите взаимодействовать (например, из основного действия в вашем приложении).

Джава:

private IUserService binder = null;

privatefinal ServiceConnection connection = new ServiceConnection() {
@Override
publicvoidonServiceConnected(ComponentName componentName, IBinder binder){
if (binder != null && binder.pingBinder()) {
binder = IUserService.Stub.asInterface(binder);
}
}

@Override
publicvoidonServiceDisconnected(ComponentName componentName){
binder = null;
}
}

Котлин:

privatevar binder: IUserService? = null

private val connection = object: ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, binder: IBinder?){
if (binder != null && binder.pingBinder()) {
binder = IUserService.Stub.asInterface(binder)
}
}

override fun onServiceDisconnected(componentName: ComponentName){
binder = null;
}
}

binder переменная — это то, что вы будете использовать для связи со Службой пользователей из вашего приложения. Чтобы проверить, доступен ли он для использования, просто убедитесь, что он не равен нулю и что pingBinder() возвращает истинный, как в примере кода выше.

Создание аргументов пользовательской службы

Прежде чем вы сможете управлять Пользовательской службой, вам необходимо определить некоторые аргументы, которые Шизуку будет использовать при ее запуске и остановке. К ним относятся такие вещи, как фактическое сообщение Шизуку имени класса службы, указание суффикса процесса, возможность отладки или нет и какая у нее версия.

Джава:

privatefinal Shizuku.UserServiceArgs serviceArgs = new Shizuku.UserServiceArgs(
newComponentName(BuildConfig.APPLICATION_ID, UserService.class.getName()))
.processNameSuffix("user_service")
.debuggable(BuildConfig.DEBUG)
.version(BuildConfig.VERSION_CODE);

Котлин:

private val serviceArgs = Shizuku.UserServiceArgs(
ComponentName(BuildConfig.APPLICATION_ID, UserService.class::java.getName()))
.processNameSuffix("user_service")
.debuggable(BuildCOnfig.DEBUG)
.version(BuildConfig.VERSION_CODE)

Запуск, остановка и привязка пользовательской службы

Действия запуска и привязки, а также действия остановки и отмены привязки объединены в Shizuku. Не существует отдельных методов запуска и привязки, а также методов остановки и отмены привязки.

Вот как начать и связать Служба пользователей.

Джава:

if (Shizuku.getVersion >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection);
} else {
//Tell the user to upgrade Shizuku.
}

Котлин:

if (Shizuku.getVersion() >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection)
} else {
//Tell the user to upgrade Shizuku.
}

Вот как остановиться и развязать Служба пользователей.

Джава:

if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}

Котлин:

if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}

Вызов пользовательской службы

После запуска Службы пользователей вы можете начать ее использовать. Просто проверьте, binder переменная не равна нулю и доступна для проверки связи, а затем выполните вызов метода.

Джава:

if (binder != null && binder.pingBinder()) {
binder.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);
}

Котлин:

if (binder?.pingBinder() == true) {
binder?.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
}

Заключение

Если вы выполнили все это, теперь у вас должна быть работающая интеграция с Shizuku. Просто не забудьте попросить своих пользователей установить Shizuku и должным образом проверить, доступен ли Shizuku, прежде чем пытаться его использовать.