diff --git a/.gitignore b/.gitignore index b1a6202..38e98fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ *.iml -/.gradle -/local.properties -/.idea +.gradle +local.properties +.idea .DS_Store -/build -/captures +build +captures .externalNativeBuild .cxx -local.properties -/app/build -/app/release +app/build +app/release +app/debug .androidide diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bc2fbdd..2345c53 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,6 +34,7 @@ android { } buildFeatures { compose = true + aidl = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.1" @@ -56,4 +57,6 @@ dependencies { implementation("com.google.accompanist:accompanist-drawablepainter:0.35.0-alpha") implementation("androidx.compose.material3:material3:1.2.0") implementation("androidx.navigation:navigation-compose:2.7.7") + implementation("dev.rikka.shizuku:provider:13.1.5") + implementation("dev.rikka.shizuku:api:13.1.5") } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3f41e6a..55542ee 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -12,6 +12,7 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} +-keep class com.bintianqi.owndroid.dpm.ShizukuService # Uncomment this to preserve the line number information for # debugging stack traces. @@ -19,4 +20,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. --renamesourcefileattribute SourceFile \ No newline at end of file +# -renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 12cc4ac..292fa7a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + + diff --git a/app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl b/app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl new file mode 100644 index 0000000..9cff5e0 --- /dev/null +++ b/app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl @@ -0,0 +1,7 @@ +package com.bintianqi.owndroid; + +interface IUserService { + void destroy() = 16777114; + String execute(String command) = 1; + String getUid() = 2; +} diff --git a/app/src/main/assets/rish.sh b/app/src/main/assets/rish.sh deleted file mode 100644 index 1aa980f..0000000 --- a/app/src/main/assets/rish.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/system/bin/sh -BASEDIR=$(dirname "$0") -DEX="$BASEDIR"/rish_shizuku.dex -[ -z "$RISH_APPLICATION_ID" ] && export RISH_APPLICATION_ID="com.bintianqi.owndroid" -/system/bin/app_process -Djava.class.path="$DEX" /system/bin --nice-name=rish rikka.shizuku.shell.ShizukuShellLoader "$@" diff --git a/app/src/main/assets/rish_shizuku.dex b/app/src/main/assets/rish_shizuku.dex deleted file mode 100644 index 964d7a5..0000000 Binary files a/app/src/main/assets/rish_shizuku.dex and /dev/null differ diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt index 7b7a69a..174a76e 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -25,4 +25,3 @@ fun isDeviceOwner(dpm: DevicePolicyManager): Boolean { fun isProfileOwner(dpm: DevicePolicyManager): Boolean { return dpm.isProfileOwnerApp("com.bintianqi.owndroid") } - diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt index 677bbe2..bc6a963 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt @@ -3,83 +3,87 @@ package com.bintianqi.owndroid.dpm import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context -import android.content.Context.MODE_PRIVATE +import android.content.ServiceConnection +import android.content.pm.PackageManager import android.os.Binder import android.os.Build.VERSION +import android.os.IBinder +import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.SpringSpec -import androidx.compose.foundation.focusable import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Warning import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import com.bintianqi.owndroid.IUserService import com.bintianqi.owndroid.R import com.bintianqi.owndroid.Receiver -import kotlinx.coroutines.Dispatchers +import com.bintianqi.owndroid.backToHome +import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.apache.commons.io.IOUtils -import java.io.* +import rikka.shizuku.Shizuku @Composable fun ShizukuActivate(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext, Receiver::class.java) - val focusMgr = LocalFocusManager.current - val filesDir = myContext.filesDir - LaunchedEffect(Unit){ extractRish(myContext) } val coScope = rememberCoroutineScope() - val scrollState = rememberScrollState() val outputTextScrollState = rememberScrollState() + var enabled by remember{ mutableStateOf(false) } + var bindShizuku by remember{ mutableStateOf(false) } + var outputText by remember{mutableStateOf("")} + LaunchedEffect(Unit){ + if(service==null){userServiceControl(myContext, true)} + while(true){ + if(service==null){ + enabled = false + bindShizuku = checkShizukuStatus()==1 + }else{ + enabled = true + bindShizuku = false + } + delay(200) + } + } Column( - modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(scrollState), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 8.dp) + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ){ - var outputText by remember{mutableStateOf("")} - if(Binder.getCallingUid()/100000!=0){ - Row{ - Icon(imageVector = Icons.Rounded.Warning, contentDescription = null, tint = colorScheme.onErrorContainer) - Text(text = stringResource(R.string.not_primary_user_not_support_shizuku), color = colorScheme.onErrorContainer) + AnimatedVisibility(bindShizuku) { + Button( + onClick = { + userServiceControl(myContext, true) + outputText = "" + } + ){ + Text(stringResource(R.string.bind_shizuku)) } } + Button( onClick = { + outputText = checkPermission(myContext) coScope.launch { - scrollState.animateScrollTo(scrollState.maxValue, scrollAnim()) outputTextScrollState.animateScrollTo(0, scrollAnim()) - val getUid = executeCommand(myContext, "sh rish.sh","id -u",null,filesDir) - outputText = if(getUid.contains("2000")){ - myContext.getString(R.string.shizuku_activated_shell) - }else if(getUid.contains("0")){ - myContext.getString(R.string.shizuku_activated_root) - }else if(getUid.contains("Error: 1")){ - myContext.getString(R.string.shizuku_not_started) - }else{ - getUid - } } } ) { @@ -89,11 +93,11 @@ fun ShizukuActivate(){ Button( onClick = { coScope.launch{ - outputText= executeCommand(myContext, "sh rish.sh","dpm list-owners",null,filesDir) - scrollState.animateScrollTo(scrollState.maxValue, scrollAnim()) + outputText = service!!.execute("dpm list-owners") outputTextScrollState.animateScrollTo(0, scrollAnim()) } - } + }, + enabled = enabled ) { Text(text = stringResource(R.string.list_owners)) } @@ -105,11 +109,13 @@ fun ShizukuActivate(){ Button( onClick = { coScope.launch{ - outputText = executeCommand(myContext, "sh rish.sh", myContext.getString(R.string.dpm_activate_da_command), null, filesDir) - scrollState.animateScrollTo(scrollState.maxValue, scrollAnim()) + outputText = service!!.execute(myContext.getString(R.string.dpm_activate_da_command)) outputTextScrollState.animateScrollTo(0, scrollAnim()) + delay(600) + if(myDpm.isAdminActive(myComponent)){backToHome=true} } - } + }, + enabled = enabled ) { Text(text = stringResource(R.string.activate_device_admin)) } @@ -118,11 +124,13 @@ fun ShizukuActivate(){ Button( onClick = { coScope.launch{ - outputText = executeCommand(myContext, "sh rish.sh", myContext.getString(R.string.dpm_activate_po_command), null, filesDir) - scrollState.animateScrollTo(scrollState.maxValue, scrollAnim()) + outputText = service!!.execute(myContext.getString(R.string.dpm_activate_po_command)) outputTextScrollState.animateScrollTo(0, scrollAnim()) + delay(600) + if(isProfileOwner(myDpm)){backToHome=true} } - } + }, + enabled = enabled ) { Text(text = stringResource(R.string.activate_profile_owner)) } @@ -130,11 +138,13 @@ fun ShizukuActivate(){ Button( onClick = { coScope.launch{ - outputText = executeCommand(myContext, "sh rish.sh", myContext.getString(R.string.dpm_activate_do_command), null, filesDir) - scrollState.animateScrollTo(scrollState.maxValue, scrollAnim()) + outputText = service!!.execute(myContext.getString(R.string.dpm_activate_do_command)) outputTextScrollState.animateScrollTo(0, scrollAnim()) + delay(600) + if(isDeviceOwner(myDpm)){backToHome=true} } - } + }, + enabled = enabled ) { Text(text = stringResource(R.string.activate_device_owner)) } @@ -143,43 +153,29 @@ fun ShizukuActivate(){ } if( - VERSION.SDK_INT>=30&&!isDeviceOwner(myDpm)&&!myDpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)&& - !myDpm.isOrganizationOwnedDeviceWithManagedProfile + VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent) ){ Column { - Text(text = stringResource(R.string.org_owned_work_profile), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) - Text(text = stringResource(R.string.input_userid_of_work_profile)) - var inputUserID by remember{mutableStateOf("")} - OutlinedTextField( - value = inputUserID, onValueChange = {inputUserID=it}, - label = {Text("UserID")}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) Button( onClick = { coScope.launch{ - focusMgr.clearFocus() - outputText = executeCommand( - myContext, "sh rish.sh", myContext.getString(R.string.activate_org_profile_command_with_user_id, inputUserID), - null, filesDir + val userID = Binder.getCallingUid() / 100000 + outputText = service!!.execute( + "dpm mark-profile-owner-on-organization-owned-device --user $userID com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver" ) - scrollState.animateScrollTo(scrollState.maxValue, scrollAnim()) outputTextScrollState.animateScrollTo(0, scrollAnim()) - if(myDpm.isOrganizationOwnedDeviceWithManagedProfile){ - Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - } } }, - modifier = Modifier.fillMaxWidth() + enabled = enabled ) { - Text(text = stringResource(R.string.activate)) + Text(text = stringResource(R.string.activate_org_profile)) } } } - SelectionContainer(modifier = Modifier.align(Alignment.Start).horizontalScroll(outputTextScrollState)){ + SelectionContainer(modifier = Modifier + .align(Alignment.Start) + .horizontalScroll(outputTextScrollState)){ Text(text = outputText, softWrap = false, modifier = Modifier.padding(4.dp)) } @@ -194,48 +190,53 @@ fun scrollAnim( visibilityThreshold: T? = null ): SpringSpec = SpringSpec(dampingRatio, stiffness, visibilityThreshold) -fun extractRish(myContext:Context){ - val assetsMgr = myContext.assets - myContext.deleteFile("rish.sh") - myContext.deleteFile("rish_shizuku.dex") - val shInput = assetsMgr.open("rish.sh") - val shOutput = myContext.openFileOutput("rish.sh",MODE_PRIVATE) - IOUtils.copy(shInput,shOutput) - shOutput.close() - val dexInput = assetsMgr.open("rish_shizuku.dex") - val dexOutput = myContext.openFileOutput("rish_shizuku.dex",MODE_PRIVATE) - IOUtils.copy(dexInput,dexOutput) - dexOutput.close() - if(VERSION.SDK_INT>=34){ Runtime.getRuntime().exec("chmod 400 rish_shizuku.dex",null,myContext.filesDir) } +private fun checkPermission(context: Context):String{ + if(checkShizukuStatus()==-1){return context.getString(R.string.shizuku_not_started)} + val getUid = if(service==null){return context.getString(R.string.shizuku_not_bind)}else{service!!.uid} + return when(getUid){ + "2000"->context.getString(R.string.shizuku_activated_shell) + "0"->context.getString(R.string.shizuku_activated_root) + else->context.getString(R.string.unknown_status)+"\nUID: $getUid" + } } -suspend fun executeCommand(myContext: Context, command: String, subCommand:String, env: Array?, dir:File?): String { - var result = "" - val tunnel:ByteArrayInputStream - val process:Process - val outputStream:OutputStream - try { - tunnel = ByteArrayInputStream(subCommand.toByteArray()) - process = withContext(Dispatchers.IO){Runtime.getRuntime().exec(command, env, dir)} - outputStream = process.outputStream - IOUtils.copy(tunnel,outputStream) - withContext(Dispatchers.IO){ outputStream.close() } - val exitCode = withContext(Dispatchers.IO){ process.waitFor() } - if(exitCode!=0){ result+="Error: $exitCode" } - }catch(e:Exception){ - e.printStackTrace() - return e.toString() - } - try { - val outputReader = BufferedReader(InputStreamReader(process.inputStream)) - var outputLine: String - while(withContext(Dispatchers.IO){ outputReader.readLine() }.also {outputLine = it}!=null) { result+="$outputLine\n" } - val errorReader = BufferedReader(InputStreamReader(process.errorStream)) - var errorLine: String - while(withContext(Dispatchers.IO){ errorReader.readLine() }.also {errorLine = it}!=null) { result+="$errorLine\n" } - } catch(e: NullPointerException) { - e.printStackTrace() - } - if(result==""){ return myContext.getString(R.string.try_again) } - return result +fun checkShizukuStatus():Int{ + val status = try { + if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { 1 } + else if (Shizuku.shouldShowRequestPermissionRationale()) { 0 } + else { Shizuku.requestPermission(0); 0 } + }catch(e:Exception){ -1 } + Log.e("Shizuku",status.toString()) + return status +} + +fun userServiceControl(context:Context, status:Boolean){ + if(checkShizukuStatus()!=1){ return } + val userServiceConnection = object : ServiceConnection { + override fun onServiceConnected(componentName: ComponentName, binder: IBinder) { + if (binder.pingBinder()) { + service = IUserService.Stub.asInterface(binder) + } else { + Toast.makeText(context,context.getString(R.string.invalid_binder),Toast.LENGTH_SHORT).show() + } + } + override fun onServiceDisconnected(componentName: ComponentName) { + service = null + Toast.makeText(context,context.getString(R.string.shizuku_service_disconnected),Toast.LENGTH_SHORT).show() + } + } + val userServiceArgs = Shizuku.UserServiceArgs( + ComponentName( + context.packageName,ShizukuService::class.java.name + ) + ) + .daemon(false) + .processNameSuffix("service") + .debuggable(true) + .version(26) + if(status){ + Shizuku.bindUserService(userServiceArgs,userServiceConnection) + }else{ + Shizuku.unbindUserService(userServiceArgs,userServiceConnection,false) + } } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuService.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuService.kt new file mode 100644 index 0000000..d967578 --- /dev/null +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuService.kt @@ -0,0 +1,46 @@ +package com.bintianqi.owndroid.dpm + +import android.os.IBinder +import android.system.Os +import com.bintianqi.owndroid.IUserService +import java.io.BufferedReader +import java.io.InputStreamReader + +var service:IUserService? = null + +class ShizukuService: IUserService.Stub() { + override fun asBinder(): IBinder { + TODO("Not yet implemented") + } + + override fun destroy(){ } + + override fun execute(command: String?): String { + var result = "" + val process:Process + try { + process = Runtime.getRuntime().exec(command) + val exitCode = process.waitFor() + if(exitCode!=0){ result+="Error: $exitCode" } + }catch(e:Exception){ + e.printStackTrace() + return e.toString() + } + try { + val outputReader = BufferedReader(InputStreamReader(process.inputStream)) + var outputLine: String + while(outputReader.readLine().also {outputLine = it}!=null) { result+="$outputLine\n" } + val errorReader = BufferedReader(InputStreamReader(process.errorStream)) + var errorLine: String + while(errorReader.readLine().also {errorLine = it}!=null) { result+="$errorLine\n" } + } catch(e: NullPointerException) { + e.printStackTrace() + } + if(result==""){ return "No result" } + return result + } + + override fun getUid(): String { + return Os.getuid().toString() + } +} diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cfa98ee..cb99ebf 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -41,6 +41,7 @@ 效果未知 选项 复制代码 + 未知状态 Device admin @@ -83,6 +84,12 @@ 已授权(Root) 激活Profile owner 激活Device owner + 激活由组织拥有的工作资料 + Shizuku服务断开连接 + Shizuku未连接 + 无效Binder + 连接Shizuku + 未授权 系统 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8d7d770..3174665 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,7 @@ Copy Command Package name Not exist + Unknown status Device admin @@ -87,9 +88,6 @@ Enter UserID of work profile List owners Shizuku not started. - - dpm mark-profile-owner-on-organization-owned-device --user %1$s com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver - dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver dpm set-profile-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver @@ -97,6 +95,12 @@ Permission granted (Root) Activate profile owner Activate device owner + Activate organization-owned work profile + Shizuku service disconnected + Invalid binder + Connect Shizuku + Shizuku disconnected + Permission denied System manager