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

@@ -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
}
}