Bind Shizuku service before navigate to it

Fix CI building
This commit is contained in:
BinTianqi
2025-01-22 22:19:06 +08:00
parent a21db0da70
commit 0b90d7c0f3
12 changed files with 110 additions and 64 deletions

View File

@@ -1,6 +1,9 @@
package com.bintianqi.owndroid;
import android.accounts.Account;
interface IUserService {
String execute(String command) = 1;
int getUid() = 2;
Account[] listAccounts() = 3;
}

View File

@@ -59,6 +59,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.dpm.AccountsViewer
import com.bintianqi.owndroid.dpm.AffiliationID
import com.bintianqi.owndroid.dpm.AlwaysOnVPNPackage
import com.bintianqi.owndroid.dpm.ApplicationManage
@@ -213,7 +214,8 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable(route = "HomePage") { HomePage(navCtrl) }
composable(route = "Permissions") { Permissions(navCtrl) }
composable(route = "Shizuku") { Shizuku(vm, navCtrl) }
composable(route = "Shizuku") { Shizuku(navCtrl, it.arguments!!) }
composable(route = "AccountsViewer") { AccountsViewer(navCtrl, it.arguments!!) }
composable(route = "DeviceAdmin") { DeviceAdmin(navCtrl) }
composable(route = "ProfileOwner") { ProfileOwner(navCtrl) }
composable(route = "DeviceOwner") { DeviceOwner(navCtrl) }
@@ -360,7 +362,7 @@ private fun HomePage(navCtrl:NavHostController) {
val profileOwner = context.isProfileOwner
val refreshStatus by dhizukuErrorStatus.collectAsState()
LaunchedEffect(refreshStatus) {
activated = context.isDeviceAdmin
activated = context.isProfileOwner || context.isDeviceOwner
activateType = if(sharedPref.getBoolean("dhizuku", false)) context.getString(R.string.dhizuku) + " - " else ""
activateType += context.getString(
if(deviceOwner) { R.string.device_owner }

View File

@@ -2,7 +2,6 @@ package com.bintianqi.owndroid
import android.content.Context
import android.os.Build
import android.os.IBinder
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -12,7 +11,6 @@ class MyViewModel: ViewModel() {
val theme = MutableStateFlow(ThemeSettings())
val installedPackages = mutableListOf<PackageInfo>()
val selectedPackage = MutableStateFlow("")
val shizukuBinder = MutableStateFlow<IBinder?>(null)
var initialized = false
fun initialize(context: Context) {

View File

@@ -1,5 +1,6 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.ClipData
import android.content.ClipboardManager
@@ -85,3 +86,8 @@ val Long.humanReadableDate: String
fun Context.showOperationResultToast(success: Boolean) {
Toast.makeText(this, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
}
@SuppressLint("PrivateApi")
fun getContext(): Context {
return Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null) as Context
}

View File

@@ -9,6 +9,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build.VERSION
import android.os.Bundle
import android.os.RemoteException
import android.os.UserManager
import android.widget.Toast
@@ -29,6 +30,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.backToHomeStateFlow
@@ -54,6 +56,7 @@ fun Permissions(navCtrl: NavHostController) {
val profileOwner = context.isProfileOwner
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
var dialog by remember { mutableIntStateOf(0) }
var bindingShizuku by remember { mutableStateOf(false) }
val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) dpm.enrollmentSpecificId else ""
MyScaffold(R.string.permissions, 0.dp, navCtrl) {
if(!dpm.isDeviceOwnerApp(context.packageName)) {
@@ -82,8 +85,19 @@ fun Permissions(navCtrl: NavHostController) {
}
FunctionItem(R.string.shizuku) {
try {
if(Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { navCtrl.navigate("Shizuku") }
else if(Shizuku.shouldShowRequestPermissionRationale()) {
if(Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
bindingShizuku = true
val destination = navCtrl.graph.findNode("Shizuku")!!.id
bindShizukuService(context, { binder ->
val args = Bundle()
args.putBinder("binder", binder)
bindingShizuku = false
navCtrl.navigate(destination, args)
}, {
Toast.makeText(context, R.string.shizuku_service_disconnected, Toast.LENGTH_SHORT).show()
bindingShizuku = false
})
} else if(Shizuku.shouldShowRequestPermissionRationale()) {
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show()
} else {
Sui.init(context.packageName)
@@ -124,6 +138,11 @@ fun Permissions(navCtrl: NavHostController) {
FunctionItem(R.string.transfer_ownership, icon = R.drawable.admin_panel_settings_fill0) { navCtrl.navigate("TransferOwnership") }
}
}
if(bindingShizuku) {
Dialog(onDismissRequest = { bindingShizuku = false }) {
CircularProgressIndicator()
}
}
if(dialog != 0) {
var input by remember { mutableStateOf("") }
AlertDialog(

View File

@@ -1,22 +1,27 @@
package com.bintianqi.owndroid.dpm
import android.accounts.Account
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.Binder
import android.os.Build.VERSION
import android.os.Bundle
import android.os.IBinder
import android.widget.Toast
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -28,56 +33,39 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.ui.MyScaffold
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import rikka.shizuku.Shizuku
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) {
fun Shizuku(navCtrl: NavHostController, navArgs: Bundle) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val coScope = rememberCoroutineScope()
val outputTextScrollState = rememberScrollState()
var outputText by rememberSaveable { mutableStateOf("") }
var showDeviceAdminButton by remember { mutableStateOf(!context.isDeviceAdmin) }
var showDeviceOwnerButton by remember { mutableStateOf(!context.isDeviceOwner) }
var showOrgProfileOwnerButton by remember { mutableStateOf(true) }
val binder by vm.shizukuBinder.collectAsStateWithLifecycle()
val binder = navArgs.getBinder("binder")!!
var service by remember { mutableStateOf<IUserService?>(null) }
var loading by remember { mutableStateOf(true) }
LaunchedEffect(binder) {
if(binder != null && binder!!.pingBinder()) {
service = IUserService.Stub.asInterface(binder)
loading = false
} else {
service = null
}
}
LaunchedEffect(service) {
if(service == null && !loading) navCtrl.navigateUp()
}
LaunchedEffect(Unit) {
if(binder == null) bindShizukuService(context, vm.shizukuBinder)
service = if(binder.pingBinder()) {
IUserService.Stub.asInterface(binder)
} else {
null
}
}
MyScaffold(R.string.shizuku, 0.dp, navCtrl, false) {
if(loading) {
Dialog(onDismissRequest = { navCtrl.navigateUp() }) {
CircularProgressIndicator()
}
}
Button(
onClick = {
@@ -103,9 +91,16 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) {
}
Button(
onClick = {
coScope.launch{
Log.d("Shizuku", "List accounts")
try {
val accounts = service!!.listAccounts()
val dest = navCtrl.graph.findNode("AccountsViewer")!!.id
navCtrl.navigate(dest, Bundle().apply { putParcelableArray("accounts", accounts) })
} catch(_: Exception) {
outputText = service!!.execute("dumpsys account")
outputTextScrollState.animateScrollTo(0)
coScope.launch{
outputTextScrollState.animateScrollTo(0)
}
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
@@ -114,23 +109,7 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) {
}
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(showDeviceAdminButton && showDeviceOwnerButton) {
Button(
onClick = {
coScope.launch{
outputText = service!!.execute(context.getString(R.string.dpm_activate_da_command))
outputTextScrollState.animateScrollTo(0)
delay(500)
showDeviceAdminButton = !context.isDeviceAdmin
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = stringResource(R.string.activate_device_admin))
}
}
AnimatedVisibility(showDeviceOwnerButton) {
AnimatedVisibility(showDeviceOwnerButton, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Button(
onClick = {
coScope.launch{
@@ -139,8 +118,7 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) {
delay(500)
showDeviceOwnerButton = !context.isDeviceOwner
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
}
) {
Text(text = stringResource(R.string.activate_device_owner))
}
@@ -173,14 +151,17 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) {
}
}
fun bindShizukuService(context: Context, shizukuBinder: MutableStateFlow<IBinder?>) {
fun bindShizukuService(
context: Context,
onServiceConnected: (IBinder) -> Unit,
onServiceDisconnected: () -> Unit
) {
val userServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
shizukuBinder.value = binder
onServiceConnected(binder)
}
override fun onServiceDisconnected(componentName: ComponentName) {
shizukuBinder.value = null
Toast.makeText(context, R.string.shizuku_service_disconnected, Toast.LENGTH_SHORT).show()
onServiceDisconnected()
}
}
val userServiceArgs = Shizuku.UserServiceArgs(ComponentName(context, ShizukuService::class.java))
@@ -194,3 +175,24 @@ fun bindShizukuService(context: Context, shizukuBinder: MutableStateFlow<IBinder
e.printStackTrace()
}
}
@Composable
fun AccountsViewer(navCtrl: NavHostController, navArgs: Bundle) {
val accounts = navArgs.getParcelableArray("accounts") as Array<Account>
MyScaffold(R.string.accounts, 8.dp, navCtrl, false) {
accounts.forEach {
Column(
modifier = Modifier
.fillMaxWidth().padding(vertical = 4.dp)
.clip(RoundedCornerShape(15)).background(MaterialTheme.colorScheme.surfaceVariant)
) {
SelectionContainer {
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
Text(stringResource(R.string.type) + ": " + it.type)
Text(stringResource(R.string.name) + ": " + it.name)
}
}
}
}
}
}

View File

@@ -1,10 +1,18 @@
package com.bintianqi.owndroid.dpm
import android.accounts.Account
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.content.Context
import android.os.Parcelable
import android.os.UserManager
import android.system.Os
import androidx.annotation.Keep
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.getContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.lang.Class
@Keep
class ShizukuService: IUserService.Stub() {
@@ -33,4 +41,10 @@ class ShizukuService: IUserService.Stub() {
}
override fun getUid(): Int = Os.getuid()
@SuppressLint("MissingPermission")
override fun listAccounts(): Array<Account> {
val am = getContext().getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
return am.accounts
}
}

View File

@@ -122,7 +122,7 @@
<string name="activate_device_owner">Активировать владельца устройства</string>
<string name="activate_org_profile">Активировать рабочий профиль, принадлежащий организации</string>
<string name="shizuku_service_disconnected">Служба Shizuku отключена</string>
<string name="accounts">Accounts</string> <!--TODO-->
<!--Системные-->
<string name="system">Syetem</string> <!--TODO-->

View File

@@ -124,6 +124,7 @@
<string name="activate_device_owner">Cihaz Sahibini Etkinleştir</string>
<string name="activate_org_profile">Kuruluş Profili Sahibini Etkinleştir</string>
<string name="shizuku_service_disconnected">Shizuku Hizmeti Bağlantısı Kesildi</string>
<string name="accounts">Accounts</string> <!--TODO-->
<!--System-->
<string name="system">Sistem</string>

View File

@@ -118,6 +118,7 @@
<string name="activate_device_owner">激活Device owner</string>
<string name="activate_org_profile">激活由组织拥有的工作资料</string>
<string name="shizuku_service_disconnected">Shizuku服务断开连接</string>
<string name="accounts">账号</string>
<!--System-->
<string name="system">系统</string>
@@ -661,7 +662,7 @@
<string name="info_security_log">设备上不能有非附属用户</string>
<string name="info_pre_reboot_security_log">并非所有设备都支持重启前安全日志</string>
<string name="info_disable_account_management">当某个帐户类型的帐户管理被禁用时,将无法添加或删除该类型的帐户。</string>
<string name="info_frp_policy">恢复出厂设置保护(Factory reset protection)策略用于防止不受信任的重置(Fastboot或Recovery)。需要设备支持持久数据块服务(Persistent data block service)</string>
<string name="info_frp_policy">恢复出厂设置保护(Factory reset protection)策略决定哪些帐户可以解锁经过不受信任的恢复出厂设置(fastboot或recovery)的设备。\n要启用此功能设备必须支持持久数据块服务(Persistent data block service)。\n如果在开发者选项中启用了OEM解锁则FRP将被禁用。</string>
<string name="info_wipe_data_in_managed_user">此用户的所有数据将会被清除,但是用户不会被删除。</string>
<string name="info_lockdown_admin_configured_network">控制用户是否可以更改管理员配置的网络。启用此锁定后用户仍然可以配置和连接到其他Wi-Fi或使用其他Wi-Fi功能如网络共享</string>
<string name="info_minimum_wifi_security_level">指定Wi-Fi网络所需的最低安全等级。设备将无法连接到低于最低安全等级的网络。如果当前网络不满足要求则会断开连接。</string>

View File

@@ -127,10 +127,10 @@
<string name="list_accounts">List accounts</string>
<string name="shizuku_not_started">Shizuku not started. </string>
<string name="dpm_activate_do_command" translatable="false">dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver</string>
<string name="dpm_activate_da_command" translatable="false">dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver</string>
<string name="activate_device_owner">Activate Device owner</string>
<string name="activate_org_profile">Activate organization-owned work profile</string>
<string name="shizuku_service_disconnected">Shizuku service disconnected</string>
<string name="accounts">Accounts</string>
<!--System-->
<string name="system">System</string>
@@ -701,7 +701,7 @@
<string name="info_security_log">If a Device owner use this function, all users should be affiliated.</string>
<string name="info_pre_reboot_security_log">Not all devices support pre-reboot security logs.</string>
<string name="info_disable_account_management">When account management is disabled for an account type, adding or removing an account of that type will not be possible.</string>
<string name="info_frp_policy">Factory reset protection can protect the device after untrusted factory reset (in Recovery or Fastboot).\nTo enable this feature, the device must support persistent data block service.</string>
<string name="info_frp_policy">The factory reset protection policy determines which accounts can unlock a device that has gone through untrusted factory reset(fastboot or recovery).\nTo enable this feature, the device must support persistent data block service.\nFactory reset protection is disabled if OEM unlocking is enabled in Developer Options.</string>
<string name="info_wipe_data_in_managed_user">All data of this user will be wiped, but that user won\'t be removed.</string>
<string name="info_lockdown_admin_configured_network">Control whether the user can change networks configured by the admin.\nWhen this lockdown is enabled, the user can still configure and connect to other Wi-Fi networks, or use other Wi-Fi capabilities such as tethering.</string>
<string name="info_minimum_wifi_security_level">Specify the minimum security level required for Wi-Fi networks. The device may not connect to networks that do not meet the minimum security level. If the current network does not meet the minimum security level set, it will be disconnected.</string>