Como aumentar as permissões do aplicativo usando a biblioteca Shizuku

O enraizamento não é para todos. Veja como você pode obter permissões elevadas de nível de shell em seu aplicativo usando a biblioteca Shizuku.

Há vários motivos pelos quais as permissões normalmente concedidas ao seu aplicativo podem não ser suficientes. Talvez você seja como eu e goste de criar aplicativos hacky que abusam da API do Android. Algumas das APIs que uso estão bloqueadas com permissões especiais. Às vezes, apenas o usuário shell (ADB) ou o sistema pode acessá-los. Porém, há uma solução – Shizuku.

Shizuku permite chamar APIs de sistema quase diretamente e inteiramente em Java ou Kotlin. Este guia mostrará como implementar e usar o Shizuku.

O que é Shizuku?

Antes de começarmos a usar o Shizuku, pode ser útil saber exatamente o que é. Se você estiver familiarizado com Magisk, então Shizuku é semelhante. Mas em vez de gerenciar o acesso root, ele gerencia o acesso ao shell.

Shizuku executa seu próprio processo com permissões em nível de shell. A forma como o usuário ativa esse processo depende do dispositivo, da versão do Android e da escolha. Shizuku pode ser ativado através do ADB, através do ADB sem fio no dispositivo (

no Android 11 e posterior) ou por meio de acesso root. Os aplicativos que implementam o Shizuku podem então solicitar permissão para usar esse processo para realizar operações elevadas.

ShizukuDesenvolvedor: Xingchen e Rikka

Preço: Grátis.

4.1.

Download

Por que Shizuku?

Embora o acesso ao sistema em nível de shell não permita que você faça tanto quanto raiz, ainda oferece mais acesso do que um aplicativo normal daria. Além disso, a forma como o Shizuku funciona permite que você use as APIs do Android quase normalmente. Você não precisa confiar em comandos shell (embora possa, se quiser).

Se o seu aplicativo precisar de permissões especiais que só podem ser concedidas por meio do ADB (ou com root), o Shizuku e o Android 11 são uma ótima combinação. Você pode simplesmente usar o Shizuku para conceder permissões especiais totalmente no dispositivo.

Mesmo com dispositivos que não possuem Android 11, o Shizuku pode ser útil. O aplicativo fornece instruções e scripts para os usuários para que você não precise fazer isso.

Integração

Adicionar Shizuku ao seu aplicativo não é das mais simples, mas também não é difícil. Infelizmente, o documentação do desenvolvedor não está exatamente completo, mas este artigo cobre você. Veja como integrar o Shizuku ao seu aplicativo.

Dependências

O primeiro passo é adicionar as dependências do Shizuku. Em seu build.gradle de nível de módulo, adicione o seguinte ao bloco de dependências.

def shizuku_version = '11.0.3'

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

Certifique-se de atualizar a versão, se necessário. 11.0.3 é o mais recente no momento em que este artigo foi escrito.

Fornecedor

Para que o Shizuku funcione, você precisa adicionar um bloco de provedor ao manifesto do seu aplicativo. Abra AndroidManifest.xml e adicione o seguinte dentro do bloco do aplicativo.

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

Permissão

Para autorização, Shizuku usa uma permissão de tempo de execução. Iremos conceder essa permissão em breve. Por enquanto, adicione-o ao AndroidManifest.xml dentro do bloco de manifesto.

Agora que tudo isso foi adicionado, a integração básica está feita. Deixe o Gradle sincronizar o projeto e prossiga para Uso.


Uso

Checando disponibilidade

Antes de começarmos a usar o Shizuku, vamos falar sobre como ter certeza de que ele está realmente disponível para uso.

Antes de verificar se a permissão foi concedida e antes de fazer chamadas de API por meio do Shizuku, você pode ter certeza de que essas verificações e chamadas serão bem-sucedidas com o seguinte método:

Shizuku.pingBinder()

Se o Shizuku estiver instalado e funcionando, isso retornará verdadeiro. Caso contrário, retornará falso.

Concessão de permissão

Como o Shizuku usa uma permissão de tempo de execução, ela deve ser concedida ao seu aplicativo antes que você possa fazer qualquer coisa com acesso ao shell. Existem também duas versões da API em circulação, com diferentes formas de concedê-la. Esta seção mostrará como lidar com ambos.

Verificando

Antes de solicitar a permissão, o melhor a fazer é verificar se você já a possui. Se fizer isso, você pode continuar com tudo o que precisa fazer. Caso contrário, você precisará solicitá-lo antes de continuar.

Para verificar se você tem permissão para usar o Shizuku, você pode usar o seguinte. Este código pressupõe que você o esteja executando dentro de uma atividade.

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

Solicitando

Se você precisar solicitar permissão para usar o Shizuku, veja como.

SHIZUKU_CODE a variável usada abaixo deve ser um número inteiro com um valor constante (variável estática).

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

Para ouvir o resultado, você precisará substituir Activity's onRequestPermissionsResult() método. Você também precisará implementar Shizuku. OnRequestPermissionResultListener. O exemplo que vou mostrar pressupõe que sua Activity implemente essa interface, mas você pode implementá-la em uma variável, se desejar.

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

Usando APIs

Agora que o Shizuku está configurado e as permissões foram concedidas, você pode começar a chamar APIs usando o Shizuku. O processo aqui é um pouco diferente do que você está acostumado. Em vez de ligar getSystemService() e lançando para algo como WindowManager, você terá que usar as APIs internas para eles (por exemplo, IWindowManager).

Shizuku inclui um desvio para a lista negra de APIs ocultas do Android, então você não precisará se preocupar com isso ao usá-lo. Se você está curioso para saber como contornar isso sozinho, confira meu tutorial anterior.

Aqui está um exemplo rápido (usando reflexão) mostrando como você pode obter uma instância de IPackageManager e use-o para conceder permissão de tempo de execução a um aplicativo.

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

O processo para outras APIs é semelhante. Obtenha as referências à classe e sua subclasse Stub. Obtenha a referência do asInterface método para a classe Stub. Obtenha as referências aos métodos desejados na própria classe. Então, invoque o asInterface referência de método que você possui, substituindo "package" acima com qualquer serviço que você precisar. Essa instância pode então ser passada para invocações de métodos.

Dica profissional: você pode evitar completamente o uso de reflexão se instalar um SDK modificado. Confira o repositório GitHub de anggrayudi para os SDKs modificados e instruções de instalação. Com isso instalado, o código acima (em Kotlin) fica muito mais simples.

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

Qualquer API baseada em AIDL pode ser usada com esse método em qualquer lugar do seu aplicativo, desde que o Shizuku esteja em execução e seu aplicativo tenha permissão.

Atendimento ao usuário

Embora o método Binder cubra a maioria dos casos de uso, pode haver momentos em que você precise de uma API que não tenha uma interface direta do Binder. Nesse caso, você pode usar o recurso User Service em Shizuku.

Este método funciona de forma semelhante a um serviço normal no Android. Você o "inicia", comunica-se vinculando-o a um ServiceConnection e executa sua lógica na classe de serviço. A diferença é que você não está usando o serviço Android e tudo dentro do serviço é executado com permissões ADB.

Agora existem algumas limitações. O serviço do usuário é executado em um processo e usuário completamente separados, portanto, você não pode interagir com o restante do aplicativo, exceto por meio de seus próprios retornos de chamada e fichários AIDL. Como também não está sendo executado em um processo de aplicativo adequado, algumas coisas, como vincular os serviços Android, podem não funcionar corretamente.

Isso também requer o Shizuku versão 10 ou posterior. Embora a maioria das fontes do aplicativo tenha a versão 11 atualmente, você ainda deve incluir uma verificação de versão, que será incluída no exemplo.

Definindo um AIDL

Para começar, você precisará criar um novo arquivo AIDL. Você pode fazer isso no Android Studio clicando com o botão direito em qualquer coisa na visualização de arquivos do Android, passando o mouse sobre a opção "Novo" e escolhendo a opção "AIDL". Insira o nome do arquivo (por exemplo, "IUserService") e o Android Studio criará um arquivo de modelo para você.

Se você não está familiarizado com o funcionamento dos AIDLs, não deixe de conferir Documentação do Google.

Remova os métodos de modelo do AIDL e adicione um destroy() método com o ID adequado para Shizuku. Isso será chamado quando Shizuku estiver eliminando o Serviço do Usuário e deve ser usado para limpar quaisquer referências ou lógica contínua que você tenha.

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

Implementando o AIDL

A próxima coisa a fazer é implementar o 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)
}
}

Os exemplos acima assumem que você tem o API oculta do Android SDK instalado.

Configurando a conexão de serviço

Agora que o serviço do usuário está definido e implementado, é hora de configurá-lo para uso. A primeira coisa que você deve fazer é definir um ServiceConnection com o qual deseja se comunicar (por exemplo, a partir da Activity principal do seu aplicativo).

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

O binder variável é o que você usará para se comunicar com o serviço do usuário do seu aplicativo. Para verificar se está disponível para uso, basta verificar se não é nulo e se pingBinder() retorna verdadeiro, assim como no exemplo de código acima.

Criando os argumentos do serviço do usuário

Antes de poder controlar o Serviço do Usuário, você precisará definir alguns argumentos para Shizuku usar ao iniciá-lo e interrompê-lo. Isso inclui coisas como informar a Shizuku o nome da classe do serviço, especificar o sufixo do processo, se é depurável ou não e qual é a versão.

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)

Iniciando, interrompendo e vinculando o serviço do usuário

As ações de iniciar e vincular e as ações de parar e desvincular são unificadas em Shizuku. Não existem métodos de início e ligação separados ou métodos de parada e desvinculação.

Veja como iniciar e vincular o Serviço ao Usuário.

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

Veja como pare e desvincule o Serviço ao Usuário.

Java:

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

Kotlin:

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

Invocando o serviço do usuário

Depois que o serviço do usuário for iniciado, você poderá começar a usá-lo. Basta verificar se o binder variável não é nula e permite ping e, em seguida, faça sua chamada de método.

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

Conclusão

Se você seguiu tudo isso, agora deverá ter uma integração Shizuku funcional. Apenas lembre-se de dizer aos seus usuários para instalarem o Shizuku e verificar corretamente se o Shizuku está disponível antes de tentar usá-lo.