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