try using shizuku to provision

This commit is contained in:
BinTianqi
2024-02-24 20:28:08 +08:00
parent b555747f89
commit 77233bec0d
12 changed files with 371 additions and 110 deletions

View File

@@ -35,6 +35,7 @@
- 安卓设置中激活(你可以在此应用中跳转到安卓设置的激活界面) - 安卓设置中激活(你可以在此应用中跳转到安卓设置的激活界面)
- ADB命令 - ADB命令
- Shizuku本质上还是ADB激活
ADB激活命令 ADB激活命令
```shell ```shell
@@ -42,6 +43,9 @@ adb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.M
``` ```
一个设备可以同时存在多个Device admin。 一个设备可以同时存在多个Device admin。
小天才电话手表Android 8.1使用ADB激活Device admin会返回"Success",但是实际上没有效果
#### 停用 #### 停用
- 此应用的”权限“界面中停用 - 此应用的”权限“界面中停用
@@ -57,6 +61,7 @@ adb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.M
#### 激活 #### 激活
- 使用ADB激活不推荐如果能使用ADB建议激活Device owner只能有一个Profile owner - 使用ADB激活不推荐如果能使用ADB建议激活Device owner只能有一个Profile owner
- Shizuku本质上还是ADB激活
- 创建工作资料此应用会成为工作资料中的Profile owner只能有一个Profile owner - 创建工作资料此应用会成为工作资料中的Profile owner只能有一个Profile owner
- 成为Device owner后创建并管理用户此应用会成为新用户的Profile owner每个用户各有一个Profile owner - 成为Device owner后创建并管理用户此应用会成为新用户的Profile owner每个用户各有一个Profile owner
@@ -81,8 +86,9 @@ adb shell dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.
#### 激活 #### 激活
- 使用ADB激活 - 使用ADB激活
- Shizuku本质上还是ADB激活
- 恢复出厂设置并开机后使用NFC发送这个app的下载链接没试过 - 恢复出厂设置并开机后使用NFC发送这个app的下载链接没试过
- 使用Root权限往/data/system里面放一个xml文件没试过 - 使用Root权限往/data/system里面放一个xml文件可以无视当前存在的用户和账号,没试过)
ADB激活命令 ADB激活命令
@@ -110,6 +116,8 @@ MIUI需要在开发者选项中打开”USB调试安全设置
ColorOS完全不支持Device owner ColorOS完全不支持Device owner
小天才电话手表Android 8.1完全不支持Device owner
#### 停用 #### 停用
- 恢复出厂设置(这是官方推荐的做法) - 恢复出厂设置(这是官方推荐的做法)
@@ -132,6 +140,17 @@ adb shell dpm remove-active-admin com.binbin.androidowner/com.binbin.androidowne
以上三种方法停用Device owner都会同时停用Device admin 以上三种方法停用Device owner都会同时停用Device admin
### Shizuku
可以用来
- 激活Device admin
- 激活Profile owner
- 激活Device admin
- 激活由组织拥有的工作资料
局限性:不能在工作资料中使用
### 设备唯一标识码 ### 设备唯一标识码
需API31或以上 需API31或以上
@@ -508,7 +527,7 @@ API34或以上将不能在系统用户中使用WipeData如果要恢复出厂
### 创建工作资料 ### 创建工作资料
设备上不能有Device owner或Profile owner 设备上不能有Device owner
一个设备只能有一个工作资料 一个设备只能有一个工作资料
@@ -850,7 +869,7 @@ adb shell pm list users
UserID不是UID。系统用户的UserID为0其他用户包括工作资料的UserID从10开始计算 UserID不是UID。系统用户的UserID为0其他用户包括工作资料的UserID从10开始计算
序列号:每个用户都不同的序列号 序列号:每个用户都不同的序列号序列号和UserID可能是一样的但是这是两个不同的东西
### 用户操作 ### 用户操作

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.binbin.androidowner" applicationId = "com.binbin.androidowner"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 16 versionCode = 17
versionName = "3.3" versionName = "4.0-Beta"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@@ -51,7 +51,10 @@ android {
} }
dependencies { dependencies {
implementation("org.apache.commons:commons-io:1.3.2")
val shizukuVersion = "13.1.5"
implementation("dev.rikka.shizuku:api:$shizukuVersion")
implementation("dev.rikka.shizuku:provider:$shizukuVersion")
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.8.2") implementation("androidx.activity:activity-compose:1.8.2")

View File

@@ -18,6 +18,7 @@
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION"/> <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl"/>
<application <application
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@@ -72,5 +73,12 @@
<action android:name="com.binbin.androidowner.PKG_INSTALL_RESULT"/> <action android:name="com.binbin.androidowner.PKG_INSTALL_RESULT"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<provider
android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku"
android:enabled="true"
android:exported="true"
android:multiprocess="false"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
</application> </application>
</manifest> </manifest>

View File

@@ -0,0 +1,26 @@
#!/system/bin/sh
BASEDIR=$(dirname "$0")
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"
/system/bin/app_process -Djava.class.path="$DEX" /system/bin --nice-name=rish rikka.shizuku.shell.ShizukuShellLoader "$@"

Binary file not shown.

View File

@@ -40,6 +40,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.binbin.androidowner.ui.theme.AndroidOwnerTheme import com.binbin.androidowner.ui.theme.AndroidOwnerTheme
import rikka.shizuku.Shizuku
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -56,9 +57,22 @@ var caCert = byteArrayOf()
@ExperimentalMaterial3Api @ExperimentalMaterial3Api
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onDestroy() {
super.onDestroy()
if(VERSION.SDK_INT>=24){
Shizuku.removeBinderReceivedListener(ShizukuUtil.binderReceivedListener)
Shizuku.removeBinderDeadListener(ShizukuUtil.binderDeadListener)
Shizuku.removeRequestPermissionResultListener(ShizukuUtil.requestPermissionListener)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if(VERSION.SDK_INT>=24){
Shizuku.addBinderReceivedListenerSticky(ShizukuUtil.binderReceivedListener)
Shizuku.addBinderDeadListener(ShizukuUtil.binderDeadListener)
Shizuku.addRequestPermissionResultListener(ShizukuUtil.requestPermissionListener)
}
getUserIcon = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { getUserIcon = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
userIconUri = it.data?.data userIconUri = it.data?.data
if(userIconUri==null){ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() } if(userIconUri==null){ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() }
@@ -90,6 +104,7 @@ class MainActivity : ComponentActivity() {
MyScaffold() MyScaffold()
} }
} }
deleteRish(applicationContext)
} }
} }
@@ -112,7 +127,8 @@ fun MyScaffold(){
"ApplicationManage" to R.string.app_manage, "ApplicationManage" to R.string.app_manage,
"UserRestriction" to R.string.user_restrict, "UserRestriction" to R.string.user_restrict,
"Password" to R.string.password, "Password" to R.string.password,
"AppSetting" to R.string.setting "AppSetting" to R.string.setting,
"ShizukuActivate" to R.string.shizuku_activate
) )
val topBarName = topBarNameMap[backStackEntry?.destination?.route]?: R.string.app_name val topBarName = topBarNameMap[backStackEntry?.destination?.route]?: R.string.app_name
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
@@ -175,6 +191,7 @@ fun MyScaffold(){
composable(route = "AppSetting", content = { AppSetting(navCtrl)}) composable(route = "AppSetting", content = { AppSetting(navCtrl)})
composable(route = "Network", content = {Network()}) composable(route = "Network", content = {Network()})
composable(route = "ActivateManagedProfile", content = {ActivateManagedProfile(navCtrl)}) composable(route = "ActivateManagedProfile", content = {ActivateManagedProfile(navCtrl)})
composable(route = "ShizukuActivate", content = {ShizukuActivate()})
} }
if(!inited&&jumpToActivateProfile){navCtrl.navigate("ActivateManagedProfile");inited=true} if(!inited&&jumpToActivateProfile){navCtrl.navigate("ActivateManagedProfile");inited=true}
} }
@@ -322,13 +339,14 @@ fun isProfileOwner(dpm:DevicePolicyManager): Boolean {
@SuppressLint("ModifierFactoryExtensionFunction", "ComposableModifierFactory") @SuppressLint("ModifierFactoryExtensionFunction", "ComposableModifierFactory")
@Composable @Composable
@Stable @Stable
fun sections(bgColor:Color=MaterialTheme.colorScheme.primaryContainer):Modifier{ fun sections(bgColor:Color=MaterialTheme.colorScheme.primaryContainer,onClick:()->Unit={},clickable:Boolean=false):Modifier{
val backgroundColor = if(isSystemInDarkTheme()){bgColor.copy(0.3F)}else{bgColor.copy(0.8F)} val backgroundColor = if(isSystemInDarkTheme()){bgColor.copy(0.3F)}else{bgColor.copy(0.8F)}
return if(!LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE).getBoolean("isWear",false)){ return if(!LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE).getBoolean("isWear",false)){
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
.clip(RoundedCornerShape(14.dp)) .clip(RoundedCornerShape(14.dp))
.clickable(onClick=onClick, enabled = clickable)
.background(color = backgroundColor) .background(color = backgroundColor)
.padding(vertical = 10.dp, horizontal = 10.dp) .padding(vertical = 10.dp, horizontal = 10.dp)
}else{ }else{
@@ -336,6 +354,7 @@ fun sections(bgColor:Color=MaterialTheme.colorScheme.primaryContainer):Modifier{
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 3.dp, vertical = 3.dp) .padding(horizontal = 3.dp, vertical = 3.dp)
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.clickable(onClick=onClick, enabled = clickable)
.background(color = backgroundColor) .background(color = backgroundColor)
.padding(vertical = 2.dp, horizontal = 3.dp) .padding(vertical = 2.dp, horizontal = 3.dp)
} }

View File

@@ -91,7 +91,7 @@ fun ManagedProfile() {
Text(text = "把上面命令中的USER_ID替换成你的UserID", style = bodyTextStyle) Text(text = "把上面命令中的USER_ID替换成你的UserID", style = bodyTextStyle)
} }
} }
if(!isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)))){ if(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))){
Column(modifier = sections()) { Column(modifier = sections()) {
Text(text = "工作资料", style = typography.titleLarge, color = titleColor) Text(text = "工作资料", style = typography.titleLarge, color = titleColor)
var skipEncrypt by remember{mutableStateOf(false)} var skipEncrypt by remember{mutableStateOf(false)}

View File

@@ -15,12 +15,11 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material.icons.Icons
import androidx.compose.material3.ButtonDefaults import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -51,6 +50,13 @@ fun DpmPermissions(navCtrl:NavHostController){
modifier = Modifier.verticalScroll(rememberScrollState()), modifier = Modifier.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Row(
modifier = sections(onClick = {navCtrl.navigate("ShizukuActivate")}, clickable = true),
horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically
){
Text(text = "Shizuku", style = typography.titleLarge, color = titleColor)
Icon(imageVector = Icons.Default.KeyboardArrowRight,contentDescription = null, tint = colorScheme.onPrimaryContainer)
}
if(!myDpm.isAdminActive(myComponent)&&isWear){ if(!myDpm.isAdminActive(myComponent)&&isWear){
Button(onClick = { activateDeviceAdmin(myContext,myComponent) },modifier = Modifier.padding(horizontal = 3.dp).fillMaxWidth()) { Button(onClick = { activateDeviceAdmin(myContext,myComponent) },modifier = Modifier.padding(horizontal = 3.dp).fillMaxWidth()) {
Text("激活Device admin") Text("激活Device admin")
@@ -81,97 +87,70 @@ fun DpmPermissions(navCtrl:NavHostController){
} }
} }
} }
if(!isda){ if(!isda&&!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){
Column( SelectionContainer(modifier = sections(colorScheme.tertiaryContainer)){
modifier = sections(colorScheme.tertiaryContainer.copy(alpha = 0.8F)), Text("激活命令:\nadb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
horizontalAlignment = Alignment.Start color = colorScheme.onTertiaryContainer, style = bodyTextStyle)
}
}
if(!isDeviceOwner(myDpm)){
Row(
modifier = sections(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { ) {
SelectionContainer { Column {
Text("adb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver", Text(text = "Profile Owner", fontSize = if(!isWear){22.sp}else{20.sp},color = titleColor)
color = colorScheme.onTertiaryContainer, style = bodyTextStyle) Text(if(isProfileOwner(myDpm)){"已激活"}else{"未激活"})
} }
Text(text = "或者进入设置(原生安卓) -> 安全 -> 更多安全设置 -> 设备管理应用 -> Android Owner", style = bodyTextStyle) if(isProfileOwner(myDpm)&&VERSION.SDK_INT>=24&&!isWear){
} Button(
} onClick = {
Row( myDpm.clearProfileOwner(myComponent)
modifier = sections(), navCtrl.navigateUp()
horizontalArrangement = Arrangement.SpaceBetween, }
verticalAlignment = Alignment.CenterVertically ) {
) { Text("撤销")
Column {
Text(text = "Profile Owner", fontSize = if(!isWear){22.sp}else{20.sp},color = titleColor)
Text(if(isProfileOwner(myDpm)){"已激活"}else{"未激活"})
}
if(isProfileOwner(myDpm)&&VERSION.SDK_INT>=24&&!isWear){
Button(
onClick = {
myDpm.clearProfileOwner(myComponent)
navCtrl.navigateUp()
} }
) {
Text("撤销")
}
}
}
if(!isProfileOwner(myDpm)){
Column(
modifier = sections(colorScheme.tertiaryContainer.copy(alpha = 0.8F)),
horizontalAlignment = Alignment.Start
) {
if(!isDeviceOwner(myDpm)){
SelectionContainer {
Text("adb shell dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
color = colorScheme.onTertiaryContainer, style = bodyTextStyle)
}
Text(text = "Device owner和Profile owner不能同时存在强烈建议激活Device owner", style = bodyTextStyle)
}
if(isDeviceOwner(myDpm)){
Text(text = "Device owner创建其他用户后这个应用会成为新用户的Profile owner", style = bodyTextStyle)
}
}
}
Row(
modifier = sections(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(text = "Device Owner", fontSize = if(!isWear){22.sp}else{20.sp},color = titleColor)
Text(if(isDeviceOwner(myDpm)){"已激活"}else{"未激活"})
}
if(isDeviceOwner(myDpm)&&!isWear){
Button(
onClick = {
myDpm.clearDeviceOwnerApp("com.binbin.androidowner")
navCtrl.navigateUp()
}
) {
Text("撤销")
} }
} }
} }
if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){
Column( SelectionContainer(modifier = sections(colorScheme.tertiaryContainer)){
modifier = sections(colorScheme.tertiaryContainer.copy(alpha = 0.8F)), Text("激活命令:\nadb shell dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
horizontalAlignment = Alignment.Start
) {
SelectionContainer {
Text(text = "adb shell dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
color = colorScheme.onTertiaryContainer, style = bodyTextStyle) color = colorScheme.onTertiaryContainer, style = bodyTextStyle)
}
if(!isda){
Text(text = "使用此命令也会激活Device Admin", style = bodyTextStyle)
}
Text(text = "成为Device owner后将不能新建工作资料", style = bodyTextStyle)
} }
} }
if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)||myDpm.isAdminActive(myComponent)){
Text( if(!isProfileOwner(myDpm)){
text = "注意!在这里撤销权限不会清除配置。比如:被停用的应用会保持停用状态", Row(
color = colorScheme.onErrorContainer, modifier = sections(),
modifier = sections(colorScheme.errorContainer.copy(alpha = 0.8F)), horizontalArrangement = Arrangement.SpaceBetween,
style = bodyTextStyle verticalAlignment = Alignment.CenterVertically
) ) {
Column {
Text(text = "Device Owner", fontSize = if(!isWear){22.sp}else{20.sp},color = titleColor)
Text(if(isDeviceOwner(myDpm)){"已激活"}else{"未激活"})
}
if(isDeviceOwner(myDpm)&&!isWear){
Button(
onClick = {
myDpm.clearDeviceOwnerApp("com.binbin.androidowner")
navCtrl.navigateUp()
}
) {
Text("撤销")
}
}
}
}
if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){
SelectionContainer(modifier = sections(colorScheme.tertiaryContainer)){
Text(text = "激活命令:\nadb shell dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
color = colorScheme.onTertiaryContainer, style = bodyTextStyle)
}
} }
if(VERSION.SDK_INT>=30){ if(VERSION.SDK_INT>=30){
Column( Column(
@@ -453,3 +432,7 @@ fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){
Toast.makeText(inputContext,"不支持",Toast.LENGTH_SHORT).show() Toast.makeText(inputContext,"不支持",Toast.LENGTH_SHORT).show()
} }
} }
fun activateShizuku(){
}

View File

@@ -0,0 +1,200 @@
package com.binbin.androidowner
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build.VERSION
import android.os.UserManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
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.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
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.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import org.apache.commons.io.IOUtils
import rikka.shizuku.Shizuku
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStreamReader
@Composable
fun ShizukuActivate(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java)
val focusMgr = LocalFocusManager.current
val sharedPref = LocalContext.current.getSharedPreferences("data", MODE_PRIVATE)
val isWear = sharedPref.getBoolean("isWear",false)
val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge }
val filesDir = myContext.filesDir
Column(modifier = Modifier.verticalScroll(rememberScrollState())){
var outputText by remember{mutableStateOf("")}
if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){
Row(modifier = sections(colorScheme.errorContainer), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){
Icon(imageVector = Icons.Rounded.Warning, contentDescription = null, tint = 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)){
Column(modifier = sections()){
Text(text = "组织拥有工作资料", style = typography.titleLarge, color = colorScheme.onPrimaryContainer)
Text(text = "请输入工作资料的UserID", style = bodyTextStyle)
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.fillMaxWidth().padding(vertical = 2.dp)
)
Button(
onClick = {
extractRish(myContext)
outputText = executeCommand(
"sh rish.sh",
"dpm mark-profile-owner-on-organization-owned-device --user $inputUserID com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
null, filesDir
)
if(myDpm.isOrganizationOwnedDeviceWithManagedProfile){
Toast.makeText(myContext,"成功",Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = "激活")
}
}
}
Button(
onClick = {
extractRish(myContext)
outputText = executeCommand("sh rish.sh", "pwd", null, filesDir)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = "test")
}
SelectionContainer(modifier = Modifier.padding(3.dp)){
Text(text = outputText, style = bodyTextStyle, softWrap = false, modifier = Modifier.horizontalScroll(rememberScrollState()))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
fun extractRish(myContext:Context){
val assetsMgr = myContext.assets
val fileList = myContext.fileList()
if("rish.sh" !in fileList){
val shInput = assetsMgr.open("rish.sh")
val shOutput = myContext.openFileOutput("rish.sh",MODE_PRIVATE)
IOUtils.copy(shInput,shOutput)
shOutput.close()
}
if("rish_shizuku.dex" !in fileList){
val dexInput = assetsMgr.open("rish_shizuku.dex")
val dexOutput = myContext.openFileOutput("rish_shizuku.dex",MODE_PRIVATE)
IOUtils.copy(dexInput,dexOutput)
dexOutput.close()
}
}
fun deleteRish(myContext: Context){
myContext.deleteFile("rish.sh")
myContext.deleteFile("rish_shizuku.dex")
}
private fun checkPermission():String {
if(Shizuku.isPreV11()) {
return "有可能不支持v11以下的Shizuku\n你仍然可以尝试使用这些功能"
}else{
try {
if(Shizuku.checkSelfPermission()==PackageManager.PERMISSION_GRANTED) {
val permission = when(Shizuku.getUid()){
0->"Root"
2000->"Shell"
else->"未知权限"
}
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 {
val output = StringBuilder()
try {
val tunnel = ByteArrayInputStream(subCommand.toByteArray())
val process = Runtime.getRuntime().exec(command,env,dir)
val outputStream = process.outputStream
IOUtils.copy(tunnel,outputStream)
outputStream.close()
process.waitFor()
val reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String
while(reader.readLine().also {line = it}!=null) { output.append(line+"\n") }
} catch(e: Exception) {
e.printStackTrace()
}
return output.toString()
}

View File

@@ -0,0 +1,16 @@
package com.binbin.androidowner;
import android.content.pm.PackageManager;
import android.util.Log;
import rikka.shizuku.Shizuku;
public class ShizukuUtil {
private static void onRequestPermissionsResult(int requestCode, int grantResult) {
boolean granted = PackageManager.PERMISSION_GRANTED == grantResult;
Log.d("ShizukuUtil","RequestCode: "+requestCode);
Log.d("ShizukuUtil","GrantState: "+granted);
}
static final Shizuku.OnRequestPermissionResultListener requestPermissionListener = ShizukuUtil::onRequestPermissionsResult;
static final Shizuku.OnBinderReceivedListener binderReceivedListener = () -> Log.d("ShizukuUtil","Binder received");
static final Shizuku.OnBinderDeadListener binderDeadListener = () -> Log.e("ShizukuUtil","Binder dead");
}

View File

@@ -5,30 +5,26 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
import android.provider.MediaStore import android.provider.MediaStore
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@@ -67,7 +63,7 @@ fun UserManage() {
Text(text = "附属用户: ${myDpm.isAffiliatedUser}",style = bodyTextStyle) Text(text = "附属用户: ${myDpm.isAffiliatedUser}",style = bodyTextStyle)
} }
Spacer(Modifier.padding(vertical = if(isWear){2.dp}else{5.dp})) Spacer(Modifier.padding(vertical = if(isWear){2.dp}else{5.dp}))
Text(text = "当前UserID${getCurrentUserId()}",style = bodyTextStyle) Text(text = "当前UserID${Binder.getCallingUid()/100000}",style = bodyTextStyle)
Text(text = "当前用户序列号:${userManager.getSerialNumberForUser(android.os.Process.myUserHandle())}",style = bodyTextStyle) Text(text = "当前用户序列号:${userManager.getSerialNumberForUser(android.os.Process.myUserHandle())}",style = bodyTextStyle)
} }
@@ -381,13 +377,3 @@ private fun userOperationResultCode(result:Int): String {
else->"未知" else->"未知"
} }
} }
fun getCurrentUserId(): Int {
try {
val uh = UserHandle::class.java.getDeclaredMethod("myUserId")
uh.isAccessible = true
val userId = uh.invoke(null)
if (userId is Int) { return userId }
} catch (ignored: Exception) { }
return -1
}

View File

@@ -115,4 +115,5 @@
<string name="developing">功能开发中</string> <string name="developing">功能开发中</string>
<string name="wifi_lockdown">WiFi锁定</string> <string name="wifi_lockdown">WiFi锁定</string>
<string name="work_profile">工作资料</string> <string name="work_profile">工作资料</string>
<string name="shizuku_activate">Shizuku</string>
</resources> </resources>