Reviews

Superior Android Growth: Elevate app permissions utilizing Shizuku

Related Articles

There are plenty of causes the permissions usually granted to your app may not be sufficient. Possibly you are like me and revel in creating hacky apps that abuse the Android API. A number of the APIs I take advantage of are locked behind particular permissions. Generally solely the shell person (ADB) or system can entry them. There is a answer although — Shizuku.


Shizuku means that you can name system APIs virtually instantly, and completely in Java or Kotlin. This information will present you the right way to implement and use Shizuku.


What’s Shizuku?

Earlier than we go about utilizing Shizuku, it is likely to be useful to know what precisely it’s. In case you’re accustomed to Magisk, then Shizuku is analogous. However as an alternative of managing root entry, it manages shell entry.

Shizuku runs its personal course of with shell-level permissions. How the person prompts that course of relies on their system, Android model, and selection. Shizuku could be activated by means of ADB, by means of on-device wi-fi ADB (on Android 11 and later), or by means of root entry. Apps implementing Shizuku can then request permission to make use of that course of to carry out elevated operations.

Why Shizuku?

Whereas shell-level entry to the system would not allow you to do as a lot as root, it nonetheless provides you extra entry than a standard app would. On high of that, the best way Shizuku works helps you to use the Android APIs virtually like regular. You do not have to depend on shell instructions (though you may if you wish to).

In case your app wants particular permissions that may solely be granted by means of ADB (or with root), Shizuku and Android 11 make an excellent pairing. You possibly can simply use Shizuku to grant particular permissions absolutely on-device.

Even with units that are not on Android 11, Shizuku could be helpful. The app gives directions and scripts for customers so you do not have to.

Integration

Including Shizuku to your app is not the best, nevertheless it’s not exhausting, both. Sadly, the developer documentation is not precisely full, however this text has you coated. Here is the right way to combine Shizuku into your app.

Dependencies

Step one is so as to add the Shizuku dependencies. In your module-level construct.gradle, add the next to the dependencies block.


def shizuku_version = '11.0.3'

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

Make certain to replace the model if wanted. 11.0.3 is the most recent on the time of writing.

Supplier

To ensure that Shizuku to work, it is advisable add a supplier block to your app’s manifest. Open AndroidManifest.xml and add the next inside the appliance block.


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

Permission

For authorization, Shizuku makes use of a runtime permission. We’ll get into granting that permission in a bit. For now, add it to AndroidManifest.xml contained in the manifest block.

Now that every one of that’s added, the fundamental integration is completed. Let Gradle do a challenge sync, and proceed on to Utilization.


Utilization

Checking Availability

Earlier than going into the right way to use Shizuku, let’s discuss ensuring it is truly accessible to make use of.

Earlier than checking to see if the permission is granted, and earlier than making API calls by means of Shizuku, you may make certain these checks and calls will succeed with the next technique:

Shizuku.pingBinder()

If Shizuku is put in and operating, it will return true. In any other case, it would return false.

Granting Permission

Since Shizuku makes use of a runtime permission, it needs to be granted to your app earlier than you are able to do something with shell entry. There are additionally two API variations in circulation, with other ways of granting it. This part will present you the right way to deal with each.

Checking

Earlier than you request the permission, the most effective factor to do is to verify if you have already got it. In case you do, you may proceed with no matter it is advisable do. In any other case, you will have to request it earlier than persevering with.

To verify you probably have permission to make use of Shizuku, you need to use the next. This code is assuming you are operating it inside an Exercise.

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

Requesting

If it is advisable request permission to make use of Shizuku, this is how.

The SHIZUKU_CODE variable used under ought to be an integer with a gentle worth (static variable).

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(new String[] { ShizukuProvider.PERMISSION }, SHIZUKU_CODE);
} else {
    Shizuku.requestPermissions(SHIZUKU_CODE);
}

To pay attention for the consequence, you will have to override Exercise’s onRequestPermissionsResult() technique. You may additionally have to implement Shizuku.OnRequestPermissionResultListener. The instance I will present assumes your Exercise implements that interface, however you may implement it in a variable if you need.

Kotlin:


class ExampleActivity : 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 void onRequestPermissionResult(requestCode: Int, grantResult: Int) {
        val isGranted = grantResult == PackageManager.PERMISSION_GRANTED
        //Do stuff based mostly on the consequence.

    }
}

Java:


public class ExampleActivity extends AppCompatActivity implements Shizuku.OnRequestPermissionResultListener {
    ...

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        for (int i = 0; i < permissions.size; i++) {
            String permission = permissions[i];
            int consequence = grantResults[i];

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

    @Override
    public void onRequestPermissionResult(int requestCode, int grantResult) {
        boolean isGranted = grantResult == PackageManager.PERMISSION_GRANTED;
        //Do stuff based mostly on the consequence.
    }
}

Utilizing APIs

Now that Shizuku is about up and permissions are granted, you can begin truly calling APIs utilizing Shizuku. The method here’s a little completely different than you is likely to be used to. As a substitute of calling getSystemService() and casting to one thing like WindowManager, you will have to make use of the interior APIs for these as an alternative (e.g., IWindowManager).

Shizuku features a bypass for Android’s hidden API blacklist, so you will not want to fret about that when utilizing it. In case you’re curious the right way to bypass that your self although, take a look at my earlier tutorial.

Here is a fast instance (utilizing reflection) displaying you how one can get an occasion of IPackageManager and use it to grant an app a runtime permission.

Kotlin:


val iPmClass = Class.forName("android.content material.pm.IPackageManager")
val iPmStub = Class.forName("android.content material.pm.IPackageManager$Stub")
val asInterfaceMethod = iPmStub.getMethod("asInterface", IBinder::class.java)
val grantRuntimePermissionMethod = iPmClass.getMethod("grantRuntimePermission", String::class.java , String::class.java , Int::class.java )

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

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

Java:


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

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

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

The method for different APIs is analogous. Get the references to the category and its Stub sub-class. Get the reference to the asInterface technique for the Stub class. Get the references to the strategies you need from the category itself. Then, invoke the asInterface technique reference you’ve, changing "package deal" above with no matter service you want. That occasion can then be handed for technique invocations.

Professional-tip: you may keep away from utilizing reflection altogether in case you set up a modified SDK. Take a look at anggrayudi’s GitHub repository for the modified SDKs and set up directions. With this put in, the above code (in Kotlin) turns into a complete lot easier.


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

Any AIDL-based APIs can be utilized with this technique from anyplace in your app, so long as Shizuku is operating and your app has permission.

Consumer Service

Whereas the Binder technique covers most use-cases, there could also be instances the place you want an API that does not have a direct Binder interface. In that case, you need to use the Consumer Service characteristic in Shizuku.

This technique works equally to a standard Service in Android. You “begin” it, talk by binding to it with a ServiceConnection, and run your logic within the service class. The distinction is you are not utilizing Android’s Service, and something contained in the service runs with ADB permissions.

Now there are some limitations. The Consumer Service runs in a totally separate course of and person, so you may’t work together with the remainder of your app besides by means of your individual AIDL callbacks and Binders. Since it is also not operating in a correct app course of, some issues like binding Android Companies might not work correctly.

This additionally requires Shizuku model 10 or later. Whereas most sources for the app have model 11 at present, you must nonetheless embrace a model verify, which shall be included within the instance.

Defining an AIDL

To get began, you will have to create a brand new AIDL file. You are able to do this in Android Studio by right-clicking something within the Android file-view, hovering over the “New” choice, and selecting the “AIDL” choice. Enter the identify of the file (e.g., “IUserService”), and Android Studio will create a template file for you.

In case you aren’t accustomed to how AIDLs work, remember to take a look at Google’s documentation.

Take away the template strategies from the AIDL after which add a destroy() technique with the right ID for Shizuku. This shall be referred to as when Shizuku is killing the Consumer Service, and ought to be used to scrub up any references or ongoing logic you’ve.

Instance AIDL:


package deal tk.zwander.shizukudemo;

interface IUserService {
    void destroy() = 16777114;

    void grantPermission(String packageName, String permission, int userId) = 1;
}

Implementing the AIDL

The subsequent factor to do is definitely implement the AIDL.

Java:


public class UserService extends IUserService.Stub {
   
    public UserService() {
    }

    @Override
    public void destroy() {
       
        System.exit(0);
    }

    @Override
    public void grantPermission(String packageName, String permission, int userId) {
       
        IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package deal")).grantRuntimePermission(packageName, permission, userId);
    }
}

Kotlin:


class UserService : IUserService.Stub {
   
    constructor() {
    }

    override enjoyable destroy() {
       
        System.exit(0)
    }

    override enjoyable grantPermission(packageName: String, permission: String, userId: Int) {
       
        IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package deal")).grantRuntimePermission(packageName, permission, userId)
    }
}

The above examples assume you’ve the Android Hidden API SDK put in.

Establishing the Service Connection

Now that the Consumer Service is outlined and carried out, it is time to get it arrange to be used. The very first thing you must do is outline a ServiceConnection the place you need to talk with it (e.g., from the primary Exercise in your app).

Java:


personal IUserService binder = null;

personal closing ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder binder) {
        if (binder != null && binder.pingBinder()) {
            binder = IUserService.Stub.asInterface(binder);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        binder = null;
    }
}

Kotlin:


personal var binder: IUserService? = null

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

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

The binder variable is what you will be utilizing to speak with the Consumer Service out of your app. To verify if it is accessible to be used, simply verify that it is not null and that pingBinder() returns true, identical to within the code instance above.

Creating the Consumer Service arguments

Earlier than you may management the Consumer Service, you will have to outline some arguments for Shizuku to make use of when beginning and stopping it. These embrace issues like truly telling Shizuku the category identify of the service, specifying the method suffix, whether or not or not it is debuggable, and what model it’s.

Java:


personal closing Shizuku.UserServiceArgs serviceArgs = new Shizuku.UserServiceArgs(
    new ComponentName(BuildConfig.APPLICATION_ID, UserService.class.getName()))
        .processNameSuffix("user_service")
        .debuggable(BuildConfig.DEBUG)
        .model(BuildConfig.VERSION_CODE);

Kotlin:


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

Beginning, Stopping, and Binding the Consumer Service

The beginning and bind actions and the cease and unbind actions are unified in Shizuku. There aren’t separate begin and bind strategies or cease and unbind strategies.

Here is the right way to begin and bind the Consumer Service.

Java:


if (Shizuku.getVersion >= 10) {
 
    Shizuku.bindUserService(serviceArgs, connection);
} else {
   
}

Kotlin:


if (Shizuku.getVersion() >= 10) {
   
    Shizuku.bindUserService(serviceArgs, connection)
} else {
   
}

Here is the right way to cease and unbind the Consumer Service.

Java:


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

Kotlin:


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

Invoking the Consumer Service

As soon as the Consumer Service is began, you can begin utilizing it. Merely verify whether or not the binder variable is non-null and pingable, after which make your technique name.

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

In case you adopted by means of all of that, you must now have a working Shizuku integration. Simply keep in mind to inform your customers to put in Shizuku, and to correctly verify that Shizuku is on the market earlier than making an attempt to make use of it.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button