Jak zvýšit oprávnění aplikace pomocí knihovny Shizuku

Rooting není pro každého. Zde je návod, jak můžete získat zvýšená oprávnění na úrovni prostředí ve vaší aplikaci pomocí knihovny Shizuku.

Existuje mnoho důvodů, proč oprávnění běžně udělená vaší aplikaci nemusí stačit. Možná jste jako já a baví vás vytvářet hackery, které zneužívají Android API. Některá rozhraní API, která používám, jsou uzamčena za speciální oprávnění. Někdy k nim má přístup pouze uživatel prostředí (ADB) nebo systém. Existuje však řešení - Shizuku.

Shizuku vám umožňuje volat systémová API téměř přímo a zcela v Javě nebo Kotlinu. Tato příručka vám ukáže, jak implementovat a používat Shizuku.

Co je Shizuku?

Než se pustíme do používání Shizuku, může být užitečné vědět, co to přesně je. Pokud jste obeznámeni s Magisk, pak je Shizuku podobné. Ale místo správy přístupu root spravuje přístup k shellu.

Shizuku spouští svůj vlastní proces s oprávněními na úrovni shellu. Jak uživatel tento proces aktivuje, závisí na jeho zařízení, verzi Androidu a výběru. Shizuku lze aktivovat prostřednictvím ADB, prostřednictvím bezdrátového ADB na zařízení (

na Androidu 11 a novějším), nebo prostřednictvím přístupu root. Aplikace implementující Shizuku pak mohou požádat o povolení používat tento proces k provádění zvýšených operací.

ShizukuVývojář: Xingchen a Rikka

Cena: Zdarma.

4.1.

Stažení

Proč Shizuku?

Zatímco přístup k systému na úrovni shellu vám neumožňuje dělat tolik jako vykořenit, stále vám poskytuje větší přístup než běžná aplikace. Kromě toho vám způsob, jakým Shizuku funguje, umožňuje používat rozhraní Android API téměř jako obvykle. Nemusíte se spoléhat na příkazy shellu (ačkoli můžete, pokud chcete).

Pokud vaše aplikace potřebuje speciální oprávnění, která lze udělit pouze prostřednictvím ADB (nebo s rootem), Shizuku a Android 11 tvoří skvělé párování. Shizuku můžete použít pouze k udělení speciálních oprávnění plně na zařízení.

I se zařízeními, která nemají Android 11, může být Shizuku užitečné. Aplikace poskytuje uživatelům pokyny a skripty, takže vy nemusíte.

Integrace

Přidání Shizuku do vaší aplikace není nejjednodušší, ale není to ani těžké. Bohužel, dokumentace pro vývojáře není úplně úplný, ale tento článek vás pokryl. Zde je návod, jak integrovat Shizuku do vaší aplikace.

Závislosti

Prvním krokem je přidání závislostí Shizuku. Ve svém build.gradle na úrovni modulu přidejte do bloku závislostí následující.

def shizuku_version = '11.0.3'

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

V případě potřeby aktualizujte verzi. 11.0.3 je nejpozději v době psaní tohoto článku.

Poskytovatel

Aby Shizuku fungovalo, musíte do manifestu své aplikace přidat blok poskytovatele. Otevřete soubor AndroidManifest.xml a do bloku aplikace přidejte následující.

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

Povolení

Pro autorizaci používá Shizuku runtime oprávnění. Za chvíli se dostaneme k udělení tohoto povolení. Prozatím jej přidejte do souboru AndroidManifest.xml do bloku manifestu.

Nyní, když je vše přidáno, je základní integrace hotová. Nechte Gradle provést synchronizaci projektu a pokračujte na Použití.


Používání

Kontrola dostupnosti

Než se pustíme do toho, jak používat Shizuku, promluvme si o tom, abychom se ujistili, že je skutečně k dispozici k použití.

Než zkontrolujete, zda je oprávnění uděleno, a před provedením volání API prostřednictvím Shizuku se můžete ujistit, že tyto kontroly a volání budou úspěšné, pomocí následující metody:

Shizuku.pingBinder()

Pokud je Shizuku nainstalováno a spuštěno, vrátí se skutečný. V opačném případě vrátí hodnotu false.

Udělení povolení

Vzhledem k tomu, že Shizuku používá runtime oprávnění, musí být vaší aplikaci uděleno, než budete moci s přístupem do prostředí cokoliv dělat. V oběhu jsou také dvě verze API s různými způsoby udělení. Tato část vám ukáže, jak zvládnout obojí.

Kontrola

Než o povolení požádáte, nejlépe uděláte, když si ověříte, zda jej již máte. Pokud tak učiníte, můžete pokračovat ve všem, co potřebujete udělat. V opačném případě si jej budete muset před pokračováním vyžádat.

Chcete-li zkontrolovat, zda máte oprávnění používat Shizuku, můžete použít následující. Tento kód předpokládá, že jej spouštíte v rámci aktivity.

Kotlin:

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

Jáva:

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

Vyžadující

Pokud potřebujete požádat o povolení k použití Shizuku, zde je návod.

The SHIZUKU_CODE proměnná použitá níže by měla být celé číslo se stálou hodnotou (statická proměnná).

Kotlin:

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

Jáva:

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

Chcete-li si poslechnout výsledek, budete muset přepsat Aktivitu onRequestPermissionsResult() metoda. Budete také muset implementovat Shizuku. OnRequestPermissionResultListener. Příklad, který ukážu, předpokládá, že vaše Aktivita implementuje toto rozhraní, ale pokud chcete, můžete jej implementovat do proměnné.

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.

}
}

Jáva:

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

Pomocí rozhraní API

Nyní, když je Shizuku nastaveno a jsou udělena oprávnění, můžete začít skutečně volat API pomocí Shizuku. Postup je zde trochu jiný, než na jaký jste možná zvyklí. Místo volání getSystemService() a casting na něco podobného WindowManager, budete muset místo toho použít interní rozhraní API (např. IWindowManager).

Shizuku obsahuje obejití skrytého seznamu API pro Android, takže se o to při používání nemusíte starat. Pokud vás zajímá, jak to obejít sami, podívejte se na můj předchozí návod.

Zde je rychlý příklad (pomocí reflexe), který ukazuje, jak můžete získat instanci IPackageManager a použijte jej k udělení oprávnění k běhu aplikace.

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)

Jáva:

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

Postup pro ostatní API je podobný. Získejte odkazy na třídu a její podtřídu Stub. Získejte odkaz na asInterface metoda pro třídu Stub. Získejte odkazy na požadované metody ze samotné třídy. Poté vyvolejte asInterface odkaz na metodu, který máte, nahraďte "package" výše s jakoukoli službou, kterou potřebujete. Tato instance pak může být předána pro vyvolání metody.

Profesionální tip: Pokud nainstalujete upravenou sadu SDK, můžete se použití odrazu zcela vyhnout. Podívejte se na anggrayudiho úložiště GitHub pro upravené sady SDK a pokyny k instalaci. S tímto nainstalovaným se výše uvedený kód (v Kotlin) stává mnohem jednodušším.

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

Jakákoli rozhraní API založená na AIDL lze s touto metodou používat odkudkoli ve vaší aplikaci, pokud běží Shizuku a vaše aplikace má oprávnění.

Uživatelská služba

Zatímco metoda Binder pokrývá většinu případů použití, mohou nastat situace, kdy budete potřebovat rozhraní API, které nemá přímé rozhraní Binder. V takovém případě můžete použít funkci Uživatelské služby v Shizuku.

Tato metoda funguje podobně jako běžná služba v systému Android. „Spustíte“ ji, komunikujete tak, že se k ní navážete pomocí ServiceConnection a spustíte svou logiku ve třídě služeb. Rozdíl je v tom, že nepoužíváte službu Android a cokoli uvnitř služby běží s oprávněními ADB.

Nyní existují určitá omezení. Uživatelská služba běží v rámci zcela samostatného procesu a uživatele, takže se zbytkem aplikace nemůžete komunikovat jinak než prostřednictvím vlastních zpětných volání AIDL a Binderů. Protože také neběží ve správném procesu aplikace, některé věci, jako je vazba služeb Android, nemusí fungovat správně.

To také vyžaduje Shizuku verze 10 nebo novější. Zatímco většina zdrojů pro aplikaci má aktuálně verzi 11, měli byste stále zahrnout kontrolu verze, která bude součástí příkladu.

Definování AIDL

Chcete-li začít, budete muset vytvořit nový soubor AIDL. V Android Studio to můžete udělat tak, že kliknete pravým tlačítkem na cokoli v zobrazení souborů Android, umístíte kurzor na možnost „Nový“ a vyberete možnost „AIDL“. Zadejte název souboru (např. „IUserService“) a Android Studio vám vytvoří soubor šablony.

Pokud nevíte, jak fungují AIDL, určitě se podívejte Dokumentace Google.

Odeberte metody šablony z AIDL a poté přidejte a destroy() metoda se správným ID pro Shizuku. To bude voláno, když Shizuku zabíjí uživatelskou službu, a mělo by být použito k vyčištění všech odkazů nebo probíhající logiky, které máte.

Příklad 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.
}

Implementace AIDL

Další věc, kterou musíte udělat, je skutečně implementovat AIDL.

Jáva:

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

Výše uvedené příklady předpokládají, že máte Skryté API pro Android SDK nainstalováno.

Nastavení připojení služby

Nyní, když je uživatelská služba definována a implementována, je čas ji nastavit pro použití. První věc, kterou byste měli udělat, je definovat ServiceConnection, kde s ním chcete komunikovat (např. z hlavní aktivity ve vaší aplikaci).

Jáva:

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

The binder proměnná je to, co budete používat ke komunikaci se službou uživatele z vaší aplikace. Chcete-li zkontrolovat, zda je k dispozici k použití, zkontrolujte, zda není null a to pingBinder() se vrací skutečný, stejně jako ve výše uvedeném příkladu kódu.

Vytváření argumentů uživatelské služby

Než budete moci ovládat uživatelskou službu, budete muset definovat některé argumenty, které Shizuku použije při jejím spouštění a zastavování. Patří mezi ně věci, jako je skutečné sdělování Shizuku názvu třídy služby, určení přípony procesu, zda je nebo není laditelná a o jakou verzi se jedná.

Jáva:

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)

Spuštění, zastavení a svázání uživatelské služby

Akce spuštění a vazby a akce zastavení a zrušení vazby jsou v Shizuku sjednoceny. Neexistují samostatné metody spuštění a vazby nebo metody zastavení a zrušení vazby.

Zde je návod, jak na to spustit a svázat uživatelskou službu.

Jáva:

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

Zde je návod, jak na to zastavit a rozvázat uživatelskou službu.

Jáva:

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

Kotlin:

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

Vyvolání uživatelské služby

Jakmile je Uživatelská služba spuštěna, můžete ji začít používat. Jednoduše zkontrolujte, zda binder proměnná nemá hodnotu null a lze ji pingnout, a poté zavolejte svou metodu.

Jáva:

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

Závěr

Pokud jste to všechno dodrželi, měli byste nyní mít funkční integraci Shizuku. Jen nezapomeňte říct svým uživatelům, aby si nainstalovali Shizuku, a před pokusem o použití řádně zkontrolovali, zda je Shizuku k dispozici.