support become device owner with shizuku

This commit is contained in:
BinTianqi
2024-02-25 15:27:51 +08:00
parent a46b349912
commit 5cef8eb193
7 changed files with 184 additions and 133 deletions

View File

@@ -142,14 +142,22 @@ adb shell dpm remove-active-admin com.binbin.androidowner/com.binbin.androidowne
### Shizuku ### Shizuku
可以用来 请自己学习如何启动[Shizuku](https://github.com/RikkaApps/Shizuku)
检查权限时如果返回“请更新Shizuku”说明你的Shizuku版本小于v11建议更新Shizuku。如果你的安卓版本不支持新的Shizuku你仍然可以尝试使用下面这些功能
功能:
- 激活Device admin - 激活Device admin
- 激活Profile owner - 激活Profile owner
- 激活Device admin - 激活Device admin
- 激活由组织拥有的工作资料 - 激活[由组织拥有的工作资料](#由组织拥有的工作资料)
局限性:不能在工作资料中使用 Shizuku的本质是ADB。在安卓10或以下你还是要连接电脑激活Shizuku
不能在非主用户中使用,即使检查权限返回“已授权”
因为作者懒得研究Shizuku-API所以直接套壳了rish。因为是套壳的rish所以不支持Sui
### 设备唯一标识码 ### 设备唯一标识码

View File

@@ -73,5 +73,5 @@ dependencies {
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0")
} }

View File

@@ -1,26 +1,5 @@
#!/system/bin/sh #!/system/bin/sh
BASEDIR=$(dirname "$0") BASEDIR=$(dirname "$0")
DEX="$BASEDIR"/rish_shizuku.dex DEX="$BASEDIR"/rish_shizuku.dex
if [ ! -f "$DEX" ]; then
echo "Cannot find $DEX, please check the tutorial in Shizuku app"
exit 1
fi
if [ $(getprop ro.build.version.sdk) -ge 34 ]; then
if [ -w $DEX ]; then
echo "On Android 14+, app_process cannot load writable dex."
echo "Attempting to remove the write permission..."
echo "上面那两行是Shizuku的提示可以忽略"
echo ""
chmod 400 $DEX
fi
if [ -w $DEX ]; then
echo "Cannot remove the write permission of $DEX."
echo "You can copy to file to terminal app's private directory (/data/data/<package>, so that remove write permission is possible"
exit 1
fi
fi
[ -z "$RISH_APPLICATION_ID" ] && export RISH_APPLICATION_ID="com.binbin.androidowner" [ -z "$RISH_APPLICATION_ID" ] && export RISH_APPLICATION_ID="com.binbin.androidowner"
/system/bin/app_process -Djava.class.path="$DEX" /system/bin --nice-name=rish rikka.shizuku.shell.ShizukuShellLoader "$@" /system/bin/app_process -Djava.class.path="$DEX" /system/bin --nice-name=rish rikka.shizuku.shell.ShizukuShellLoader "$@"

View File

@@ -104,7 +104,6 @@ class MainActivity : ComponentActivity() {
MyScaffold() MyScaffold()
} }
} }
deleteRish(applicationContext)
} }
} }

View File

@@ -64,7 +64,7 @@ fun ManagedProfile() {
Text("跳转至个人应用") Text("跳转至个人应用")
} }
}else{ }else{
if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)&&!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)&&!isDeviceOwner(myDpm)){
Button( Button(
onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth() onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth()
){ ){
@@ -115,7 +115,7 @@ fun ManagedProfile() {
} }
} }
if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){ if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){
Row(modifier = sections(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ Row(modifier = sections(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){
var suspended by remember{mutableStateOf(false)} var suspended by remember{mutableStateOf(false)}
suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED
@@ -130,7 +130,7 @@ fun ManagedProfile() {
} }
} }
if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){ if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){
Column(modifier = sections()){ Column(modifier = sections()){
var time by remember{mutableStateOf("")} var time by remember{mutableStateOf("")}
time = myDpm.getManagedProfileMaximumTimeOff(myComponent).toString() time = myDpm.getManagedProfileMaximumTimeOff(myComponent).toString()

View File

@@ -55,7 +55,7 @@ fun DpmPermissions(navCtrl:NavHostController){
modifier = sections(onClick = {navCtrl.navigate("ShizukuActivate")}, clickable = true), modifier = sections(onClick = {navCtrl.navigate("ShizukuActivate")}, clickable = true),
horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically
){ ){
Text(text = "Shizuku", style = typography.titleLarge, color = titleColor) Text(text = "Shizuku", style = typography.titleLarge, color = titleColor, modifier = Modifier.padding(vertical = 2.dp))
Icon(imageVector = Icons.Default.KeyboardArrowRight,contentDescription = null, tint = colorScheme.onPrimaryContainer) Icon(imageVector = Icons.Default.KeyboardArrowRight,contentDescription = null, tint = colorScheme.onPrimaryContainer)
} }
if(!myDpm.isAdminActive(myComponent)&&isWear){ if(!myDpm.isAdminActive(myComponent)&&isWear){
@@ -74,6 +74,7 @@ fun DpmPermissions(navCtrl:NavHostController){
} }
if(!isWear) if(!isWear)
if(isda){ if(isda){
if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){
Button( Button(
onClick = { onClick = {
myDpm.removeActiveAdmin(myComponent) myDpm.removeActiveAdmin(myComponent)
@@ -82,6 +83,7 @@ fun DpmPermissions(navCtrl:NavHostController){
) { ) {
Text("撤销") Text("撤销")
} }
}
}else{ }else{
Button(onClick = { activateDeviceAdmin(myContext,myComponent) }) { Button(onClick = { activateDeviceAdmin(myContext,myComponent) }) {
Text("激活") Text("激活")

View File

@@ -5,9 +5,12 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@@ -32,12 +35,10 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import rikka.shizuku.Shizuku import rikka.shizuku.Shizuku
import java.io.BufferedReader import java.io.*
import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStreamReader
@Composable @Composable
fun ShizukuActivate(){ fun ShizukuActivate(){
@@ -49,49 +50,79 @@ fun ShizukuActivate(){
val isWear = sharedPref.getBoolean("isWear",false) val isWear = sharedPref.getBoolean("isWear",false)
val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge } val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge }
val filesDir = myContext.filesDir val filesDir = myContext.filesDir
Column(modifier = Modifier.verticalScroll(rememberScrollState())){ var launchExtractRish by remember{mutableStateOf(false)}
LaunchedEffect(launchExtractRish){ if(launchExtractRish){ extractRish(myContext);launchExtractRish=false } }
val scrollState = rememberScrollState()
Column(modifier = Modifier.verticalScroll(scrollState)){
var outputText by remember{mutableStateOf("")} var outputText by remember{mutableStateOf("")}
if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ if(Binder.getCallingUid()/100000!=0){
Row(modifier = sections(colorScheme.errorContainer), verticalAlignment = Alignment.CenterVertically){ Row(modifier = sections(colorScheme.errorContainer), verticalAlignment = Alignment.CenterVertically){
Icon(imageVector = Icons.Rounded.Warning, contentDescription = null, tint = colorScheme.onErrorContainer) Icon(imageVector = Icons.Rounded.Warning, contentDescription = null, tint = colorScheme.onErrorContainer)
Text(text = "暂不支持在工作资料中使用Shizuku", style = bodyTextStyle, color = colorScheme.onErrorContainer) Text(text = "暂不支持在非主用户中使用Shizuku", style = bodyTextStyle, color = colorScheme.onErrorContainer)
}
}
Button(onClick = {outputText=checkPermission()}, enabled = VERSION.SDK_INT>=24, modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) {
Text(text = "检查权限")
}
Column(modifier = sections()){
Text(text = "激活", style = typography.titleLarge, color = colorScheme.onPrimaryContainer)
Button(
onClick = {
extractRish(myContext)
outputText = executeCommand("sh rish.sh", "dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", null, filesDir)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Device admin")
}
Button(
onClick = {
extractRish(myContext)
outputText = executeCommand("sh rish.sh", "dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", null, filesDir)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Profile owner")
}
Button(
onClick = {
extractRish(myContext)
outputText = executeCommand("sh rish.sh", "dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", null, filesDir)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Device owner")
} }
} }
if(VERSION.SDK_INT>=30&&!myDpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)){ var launchPermissionCheck by remember{mutableStateOf(false)}
LaunchedEffect(launchPermissionCheck){
if(launchPermissionCheck){
outputText = checkPermission()
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
launchPermissionCheck=false
}
}
Button(
onClick = {launchPermissionCheck=true},
enabled = VERSION.SDK_INT>=24, modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)
) {
Text(text = "检查权限")
}
if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){
Column(modifier = sections()){
Text(text = "激活", style = typography.titleLarge, color = colorScheme.onPrimaryContainer)
if(!myDpm.isAdminActive(myComponent)){
var launchActivateDA by remember{mutableStateOf(false)}
LaunchedEffect(launchActivateDA){
if(launchActivateDA){
outputText = executeCommand("sh rish.sh", "dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", null, filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
launchActivateDA=false
}
}
Button(onClick = {launchActivateDA=true}, modifier = Modifier.fillMaxWidth()) {
Text(text = "Device admin")
}
}
var launchActivatePO by remember{mutableStateOf(false)}
LaunchedEffect(launchActivatePO){
if(launchActivatePO){
outputText = executeCommand("sh rish.sh", "dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", null, filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
launchActivatePO=false
}
}
Button(onClick = {launchActivatePO=true}, modifier = Modifier.fillMaxWidth()) {
Text(text = "Profile owner")
}
var launchActivateDO by remember{mutableStateOf(false)}
LaunchedEffect(launchActivateDO){
if(launchActivateDO){
outputText = executeCommand("sh rish.sh", "dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", null, filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
launchActivateDO=false
}
}
Button(onClick = {launchActivateDO=true}, modifier = Modifier.fillMaxWidth()) {
Text(text = "Device owner")
}
}
}
if(VERSION.SDK_INT>=30&&!isDeviceOwner(myDpm)&&!myDpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)&&!myDpm.isOrganizationOwnedDeviceWithManagedProfile){
Column(modifier = sections()){ Column(modifier = sections()){
Text(text = "组织拥有工作资料", style = typography.titleLarge, color = colorScheme.onPrimaryContainer) Text(text = "组织拥有工作资料", style = typography.titleLarge, color = colorScheme.onPrimaryContainer)
Text(text = "请输入工作资料的UserID", style = bodyTextStyle) Text(text = "请输入工作资料的UserID", style = bodyTextStyle)
@@ -103,14 +134,22 @@ fun ShizukuActivate(){
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp)
) )
Button( var launchActivateOrgProfile by remember{mutableStateOf(false)}
onClick = { LaunchedEffect(launchActivateOrgProfile){
extractRish(myContext) if(launchActivateOrgProfile){
focusMgr.clearFocus()
outputText = executeCommand( outputText = executeCommand(
"sh rish.sh", "sh rish.sh",
"dpm mark-profile-owner-on-organization-owned-device --user $inputUserID com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", "dpm mark-profile-owner-on-organization-owned-device --user $inputUserID com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
null, filesDir null, filesDir
) )
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
launchActivateOrgProfile=false
}
}
Button(
onClick = {
launchActivateOrgProfile=true
if(myDpm.isOrganizationOwnedDeviceWithManagedProfile){ if(myDpm.isOrganizationOwnedDeviceWithManagedProfile){
Toast.makeText(myContext,"成功",Toast.LENGTH_SHORT).show() Toast.makeText(myContext,"成功",Toast.LENGTH_SHORT).show()
} }
@@ -121,82 +160,106 @@ fun ShizukuActivate(){
} }
} }
} }
var launchListOwners by remember{mutableStateOf(false)}
LaunchedEffect(launchListOwners){
if(launchListOwners){
outputText=executeCommand("sh rish.sh","dpm list-owners",null,filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
launchListOwners=false
}
}
Button( Button(
onClick = { onClick = {launchListOwners=true}, modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)
extractRish(myContext)
outputText = executeCommand("sh rish.sh", "pwd", null, filesDir)
},
modifier = Modifier.fillMaxWidth()
) { ) {
Text(text = "test") Text(text = "列出Owners")
} }
SelectionContainer(modifier = Modifier.padding(3.dp)){
Text(text = outputText, style = bodyTextStyle, softWrap = false, modifier = Modifier.horizontalScroll(rememberScrollState())) var launchTest by remember{mutableStateOf(false)}
LaunchedEffect(launchTest){
if(launchTest){
outputText="下面应该出现一行包含“2000”或“0”的文本\n"
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
outputText+=executeCommand("sh rish.sh","id",null,filesDir)
launchTest=false
} }
}
Button(
onClick = {launchTest=true}, modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = "测试rish")
}
SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){
Text(text = outputText, style = bodyTextStyle, softWrap = false, modifier = Modifier.padding(4.dp))
}
Spacer(Modifier.padding(vertical = 30.dp)) Spacer(Modifier.padding(vertical = 30.dp))
} }
} }
@Stable
fun <T> scrollAnim(
dampingRatio: Float = Spring.DampingRatioNoBouncy,
stiffness: Float = Spring.StiffnessMedium,
visibilityThreshold: T? = null
): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold)
fun extractRish(myContext:Context){ fun extractRish(myContext:Context){
val assetsMgr = myContext.assets val assetsMgr = myContext.assets
val fileList = myContext.fileList() myContext.deleteFile("rish.sh")
if("rish.sh" !in fileList){ myContext.deleteFile("rish_shizuku.dex")
val shInput = assetsMgr.open("rish.sh") val shInput = assetsMgr.open("rish.sh")
val shOutput = myContext.openFileOutput("rish.sh",MODE_PRIVATE) val shOutput = myContext.openFileOutput("rish.sh",MODE_PRIVATE)
IOUtils.copy(shInput,shOutput) IOUtils.copy(shInput,shOutput)
shOutput.close() shOutput.close()
}
if("rish_shizuku.dex" !in fileList){
val dexInput = assetsMgr.open("rish_shizuku.dex") val dexInput = assetsMgr.open("rish_shizuku.dex")
val dexOutput = myContext.openFileOutput("rish_shizuku.dex",MODE_PRIVATE) val dexOutput = myContext.openFileOutput("rish_shizuku.dex",MODE_PRIVATE)
IOUtils.copy(dexInput,dexOutput) IOUtils.copy(dexInput,dexOutput)
dexOutput.close() dexOutput.close()
} if(VERSION.SDK_INT>=34){ Runtime.getRuntime().exec("chmod 400 rish_shizuku.dex",null,myContext.filesDir) }
}
fun deleteRish(myContext: Context){
myContext.deleteFile("rish.sh")
myContext.deleteFile("rish_shizuku.dex")
} }
private fun checkPermission():String { private fun checkPermission():String {
if(Shizuku.isPreV11()) { return if(Shizuku.isPreV11()) {
return "有可能不支持v11以下的Shizuku\n你仍然可以尝试使用这些功能" "请更新Shizuku"
}else{ }else{
try{ try{
if(Shizuku.checkSelfPermission()==PackageManager.PERMISSION_GRANTED) { if(Shizuku.checkSelfPermission()==PackageManager.PERMISSION_GRANTED) {
val permission = when(Shizuku.getUid()){ val permission = when(Shizuku.getUid()){ 0->"Root"; 2000->"Shell"; else->"未知权限" }
0->"Root" "Shizuku v${Shizuku.getVersion()}\n已授权($permission"
2000->"Shell" }else if(Shizuku.shouldShowRequestPermissionRationale()){ "用户拒绝" }
else->"未知权限" else{ Shizuku.requestPermission(0); "请求授权" }
}catch(e: Throwable){ "服务未启动" }
} }
return "Shizuku v${Shizuku.getVersion()}\n已授权($permission)"
} else if(Shizuku.shouldShowRequestPermissionRationale()) {
return "用户拒绝"
} else {
Shizuku.requestPermission(0)
}
} catch(e: Throwable) {
return "服务未启动"
}
}
return "未知"
} }
fun executeCommand(command: String, subCommand:String, env: Array<String>?, dir:File): String { fun executeCommand(command: String, subCommand:String, env: Array<String>?, dir:File?): String {
val output = StringBuilder() var result = ""
val tunnel:ByteArrayInputStream
val process:Process
val outputStream:OutputStream
try { try {
val tunnel = ByteArrayInputStream(subCommand.toByteArray()) tunnel = ByteArrayInputStream(subCommand.toByteArray())
val process = Runtime.getRuntime().exec(command,env,dir) process = Runtime.getRuntime().exec(command,env,dir)
val outputStream = process.outputStream outputStream = process.outputStream
IOUtils.copy(tunnel,outputStream) IOUtils.copy(tunnel,outputStream)
outputStream.close() outputStream.close()
process.waitFor() val exitCode = process.waitFor()
val reader = BufferedReader(InputStreamReader(process.inputStream)) if(exitCode!=0){ result+="出错了!退出码:$exitCode" }
var line: String
while(reader.readLine().also {line = it}!=null) { output.append(line+"\n") }
}catch(e:Exception){ }catch(e:Exception){
e.printStackTrace() e.printStackTrace()
return e.toString()
} }
return output.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()
}
return result
} }