Comment élever les autorisations des applications à l'aide de la bibliothèque Shizuku

L'enracinement n'est pas pour tout le monde. Voici comment obtenir des autorisations élevées au niveau du shell dans votre application à l'aide de la bibliothèque Shizuku.

Il existe de nombreuses raisons pour lesquelles les autorisations normalement accordées à votre application peuvent ne pas suffire. Peut-être que vous êtes comme moi et que vous aimez créer des applications hacky qui abusent de l'API Android. Certaines des API que j'utilise sont verrouillées par des autorisations spéciales. Parfois, seul l'utilisateur du shell (ADB) ou le système peut y accéder. Il existe cependant une solution: Shizuku.

Shizuku vous permet d'appeler les API système presque directement, et entièrement en Java ou Kotlin. Ce guide vous montrera comment implémenter et utiliser Shizuku.

Qu’est-ce que Shizuku?

Avant d’utiliser Shizuku, il peut être utile de savoir de quoi il s’agit exactement. Si vous connaissez Magisk, alors Shizuku est similaire. Mais au lieu de gérer l’accès root, il gère l’accès shell.

Shizuku exécute son propre processus avec des autorisations au niveau du shell. La manière dont l'utilisateur active ce processus dépend de son appareil, de la version d'Android et de son choix. Shizuku peut être activé via ADB, via ADB sans fil sur l'appareil (

sur Android 11 et versions ultérieures), ou via un accès root. Les applications implémentant Shizuku peuvent alors demander l’autorisation d’utiliser ce processus pour effectuer des opérations élevées.

ShizukuDéveloppeur: Xingchen et Rikka

Prix ​​: Gratuit.

4.1.

Télécharger

Pourquoi Shizuku?

Même si l'accès au système au niveau du shell ne vous permet pas d'en faire autant que racine, elle vous donne toujours plus d'accès qu'une application normale. En plus de cela, la façon dont Shizuku fonctionne vous permet d'utiliser les API Android presque comme d'habitude. Vous n'êtes pas obligé de vous fier aux commandes shell (même si vous le pouvez si vous le souhaitez).

Si votre application nécessite des autorisations spéciales qui ne peuvent être accordées que via ADB (ou avec root), Shizuku et Android 11 forment un excellent couple. Vous pouvez simplement utiliser Shizuku pour accorder des autorisations spéciales entièrement sur l'appareil.

Même avec des appareils qui ne fonctionnent pas sous Android 11, Shizuku peut être utile. L'application fournit des instructions et des scripts aux utilisateurs afin que vous n'ayez pas à le faire.

L'intégration

Ajouter Shizuku à votre application n’est pas le plus simple, mais ce n’est pas difficile non plus. Malheureusement, le documentation du développeur n'est pas tout à fait complet, mais cet article vous couvre. Voici comment intégrer Shizuku dans votre application.

Dépendances

La première étape consiste à ajouter les dépendances Shizuku. Dans votre build.gradle au niveau du module, ajoutez ce qui suit au bloc de dépendances.

def shizuku_version = '11.0.3'

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

Assurez-vous de mettre à jour la version si nécessaire. 11.0.3 est la dernière version au moment de la rédaction.

Fournisseur

Pour que Shizuku fonctionne, vous devez ajouter un bloc fournisseur au manifeste de votre application. Ouvrez AndroidManifest.xml et ajoutez ce qui suit dans le bloc d'application.

 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" />

Autorisation

Pour l'autorisation, Shizuku utilise une autorisation d'exécution. Nous reviendrons sur l'octroi de cette autorisation dans un instant. Pour l'instant, ajoutez-le à AndroidManifest.xml dans le bloc manifeste.

Maintenant que tout cela est ajouté, l’intégration de base est terminée. Laissez Gradle effectuer une synchronisation de projet et passez à l'utilisation.


Usage

Vérifier les disponibilités

Avant d'expliquer comment utiliser Shizuku, parlons de la nécessité de nous assurer qu'il est réellement disponible.

Avant de vérifier si l'autorisation est accordée et avant d'effectuer des appels API via Shizuku, vous pouvez vous assurer que ces vérifications et appels réussiront avec la méthode suivante :

Shizuku.pingBinder()

Si Shizuku est installé et en cours d'exécution, cela reviendra vrai. Sinon, il retournera faux.

Accorder l'autorisation

Étant donné que Shizuku utilise une autorisation d'exécution, celle-ci doit être accordée à votre application avant que vous puissiez faire quoi que ce soit avec l'accès au shell. Il existe également deux versions d'API en circulation, avec différentes manières de l'accorder. Cette section vous montrera comment gérer les deux.

Vérification

Avant de demander l’autorisation, la meilleure chose à faire est de vérifier si vous l’avez déjà. Si vous le faites, vous pouvez continuer ce que vous devez faire. Sinon, vous devrez en faire la demande avant de continuer.

Pour vérifier si vous avez l'autorisation d'utiliser Shizuku, vous pouvez utiliser ce qui suit. Ce code suppose que vous l'exécutez dans une activité.

Kotlin:

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

Java:

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

Demander

Si vous devez demander l'autorisation d'utiliser Shizuku, voici comment procéder.

Le SHIZUKU_CODE La variable utilisée ci-dessous doit être un entier avec une valeur constante (variable statique).

Kotlin:

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

Java:

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

Pour écouter le résultat, vous devrez remplacer l'activité onRequestPermissionsResult() méthode. Vous devrez également mettre en œuvre Shizuku. OnRequestPermissionResultListener. L'exemple que je vais montrer suppose que votre activité implémente cette interface, mais vous pouvez l'implémenter dans une variable si vous le souhaitez.

Kotlin:

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.

}
}

Java:

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.
}
}

Utiliser des API

Maintenant que Shizuku est configuré et que les autorisations sont accordées, vous pouvez commencer à appeler des API à l'aide de Shizuku. Le processus ici est un peu différent de celui auquel vous êtes peut-être habitué. Au lieu d'appeler getSystemService() et lancer quelque chose comme WindowManager, vous devrez plutôt utiliser les API internes pour ceux-ci (par exemple, IWindowManager).

Shizuku inclut un contournement de la liste noire des API cachées d'Android, vous n'aurez donc pas à vous en soucier lorsque vous l'utiliserez. Si vous êtes curieux de savoir comment contourner cela vous-même, consultez mon tutoriel précédent.

Voici un exemple rapide (utilisant la réflexion) vous montrant comment obtenir une instance de IPackageManager et utilisez-le pour accorder à une application une autorisation d'exécution.

Kotlin:

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)

Java:

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);

Le processus pour les autres API est similaire. Obtenez les références à la classe et à sa sous-classe Stub. Obtenez la référence du asInterface méthode pour la classe Stub. Obtenez les références aux méthodes souhaitées à partir de la classe elle-même. Ensuite, invoquez le asInterface référence de méthode dont vous disposez, en remplaçant "package" ci-dessus avec le service dont vous avez besoin. Cette instance peut ensuite être transmise pour les invocations de méthodes.

Conseil de pro : vous pouvez éviter complètement d'utiliser la réflexion si vous installez un SDK modifié. Consultez le référentiel GitHub d'anggrayudi pour les SDK modifiés et les instructions d'installation. Une fois installé, le code ci-dessus (en Kotlin) devient beaucoup plus simple.

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

Toutes les API basées sur AIDL peuvent être utilisées avec cette méthode depuis n'importe où dans votre application, à condition que Shizuku soit en cours d'exécution et que votre application dispose de l'autorisation.

Service utilisateur

Bien que la méthode Binder couvre la plupart des cas d'utilisation, il peut arriver que vous ayez besoin d'une API qui ne dispose pas d'une interface Binder directe. Dans ce cas, vous pouvez utiliser la fonctionnalité Service utilisateur dans Shizuku.

Cette méthode fonctionne de manière similaire à un service normal sous Android. Vous le "démarrez", communiquez en vous liant à lui avec un ServiceConnection et exécutez votre logique dans la classe de service. La différence est que vous n'utilisez pas le service Android et que tout ce qui se trouve à l'intérieur du service s'exécute avec les autorisations ADB.

Il existe désormais certaines limites. Le service utilisateur s'exécute dans un processus et un utilisateur complètement séparés, vous ne pouvez donc pas interagir avec le reste de votre application sauf via vos propres rappels AIDL et classeurs. Comme il ne s'exécute pas non plus dans le cadre d'un processus d'application approprié, certaines choses, comme la liaison des services Android, peuvent ne pas fonctionner correctement.

Cela nécessite également la version 10 ou ultérieure de Shizuku. Bien que la plupart des sources de l'application disposent actuellement de la version 11, vous devez toujours inclure une vérification de version, qui sera incluse dans l'exemple.

Définir une AIDL

Pour commencer, vous devrez créer un nouveau fichier AIDL. Vous pouvez le faire dans Android Studio en cliquant avec le bouton droit sur n'importe quoi dans la vue des fichiers Android, en survolant l'option "Nouveau" et en choisissant l'option "AIDL". Entrez le nom du fichier (par exemple, « IUserService ») et Android Studio créera un fichier modèle pour vous.

Si vous n'êtes pas familier avec le fonctionnement des AIDL, assurez-vous de consulter Documentation de Google.

Supprimez les méthodes de modèle de l'AIDL, puis ajoutez un destroy() méthode avec l’ID approprié pour Shizuku. Ceci sera appelé lorsque Shizuku tuera le service utilisateur et devrait être utilisé pour nettoyer toutes les références ou la logique en cours dont vous disposez.

Exemple 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.
}

Mise en œuvre de l'AIDL

La prochaine chose à faire est de mettre en œuvre l’AIDL.

Java:

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);
}
}

Kotlin:

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)
}
}

Les exemples ci-dessus supposent que vous disposez du API cachée Android SDK installé.

Configuration de la connexion au service

Maintenant que le service utilisateur est défini et implémenté, il est temps de le configurer pour pouvoir l'utiliser. La première chose à faire est de définir une ServiceConnection avec laquelle vous souhaitez communiquer (par exemple, à partir de l'activité principale de votre application).

Java:

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;
}
}

Kotlin:

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;
}
}

Le binder La variable est ce que vous utiliserez pour communiquer avec le service utilisateur à partir de votre application. Pour vérifier s'il est disponible, vérifiez simplement qu'il n'est pas nul et que pingBinder() Retour vrai, tout comme dans l'exemple de code ci-dessus.

Création des arguments du service utilisateur

Avant de pouvoir contrôler le service utilisateur, vous devrez définir certains arguments que Shizuku devra utiliser lors de son démarrage et de son arrêt. Celles-ci incluent des choses comme indiquer à Shizuku le nom de classe du service, spécifier le suffixe du processus, s'il est déboguable ou non et de quelle version il s'agit.

Java:

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

Kotlin:

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

Démarrage, arrêt et liaison du service utilisateur

Les actions de démarrage et de liaison et les actions d'arrêt et de déconnexion sont unifiées dans Shizuku. Il n’existe pas de méthodes de démarrage et de liaison distinctes ni de méthodes d’arrêt et de dissociation.

Voici comment démarrer et lier le Service Utilisateur.

Java:

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

Kotlin:

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

Voici comment arrêter et dissocier le Service Utilisateur.

Java:

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

Kotlin:

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

Appel du service utilisateur

Une fois le service utilisateur démarré, vous pouvez commencer à l'utiliser. Vérifiez simplement si le binder La variable est non nulle et peut être pingée, puis effectuez votre appel de méthode.

Java:

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

Kotlin:

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

Conclusion

Si vous avez suivi tout cela, vous devriez maintenant avoir une intégration Shizuku fonctionnelle. N'oubliez pas de demander à vos utilisateurs d'installer Shizuku et de bien vérifier que Shizuku est disponible avant d'essayer de l'utiliser.