Fine-grained Dhizuku server permissions

Add app icon to fastlane metadate
This commit is contained in:
BinTianqi
2025-08-24 18:12:31 +08:00
parent c745eb25a9
commit 38ef06e12a
3 changed files with 90 additions and 26 deletions

View File

@@ -11,8 +11,14 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -21,6 +27,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -60,8 +67,16 @@ class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuC
val file = mContext.filesDir.resolve(DHIZUKU_CLIENTS_FILE) val file = mContext.filesDir.resolve(DHIZUKU_CLIENTS_FILE)
val clients = Json.decodeFromString<List<DhizukuClientInfo>>(file.readText()) val clients = Json.decodeFromString<List<DhizukuClientInfo>>(file.readText())
val signature = getPackageSignature(packageInfo) val signature = getPackageSignature(packageInfo)
val hasPermission = DhizukuClientInfo(callingUid, signature, true) in clients val requiredPermission = when (func) {
Log.d(TAG, "UID $callingUid, PID $callingPid, has permission: $hasPermission") "remote_transact", "remote_process" -> func
"bind_user_service", "unbind_user_service" -> "user_service"
"get_delegated_scopes", "set_delegated_scopes" -> "delegated_scopes"
else -> "other"
}
val hasPermission = clients.find {
callingUid == it.uid && signature == it.signature && requiredPermission in it.permissions
} != null
Log.d(TAG, "UID $callingUid, PID $callingPid, required permission: $requiredPermission, has permission: $hasPermission")
return hasPermission return hasPermission
} }
@@ -91,9 +106,12 @@ class DhizukuActivity : ComponentActivity() {
val label = appInfo.loadLabel(packageManager).toString() val label = appInfo.loadLabel(packageManager).toString()
fun close(grantPermission: Boolean) { fun close(grantPermission: Boolean) {
val file = filesDir.resolve(DHIZUKU_CLIENTS_FILE) val file = filesDir.resolve(DHIZUKU_CLIENTS_FILE)
val clients = Json.decodeFromString<MutableList<DhizukuClientInfo>>(file.readText()) val json = Json { ignoreUnknownKeys = true }
val clients = json.decodeFromString<MutableList<DhizukuClientInfo>>(file.readText())
val index = clients.indexOfFirst { it.uid == uid } val index = clients.indexOfFirst { it.uid == uid }
val clientInfo = DhizukuClientInfo(uid, getPackageSignature(packageInfo), grantPermission) val clientInfo = DhizukuClientInfo(
uid, getPackageSignature(packageInfo), if (grantPermission) DhizukuPermissions else emptyList()
)
if (index == -1) clients += clientInfo if (index == -1) clients += clientInfo
else clients[index] = clientInfo else clients[index] = clientInfo
file.writeText(Json.encodeToString(clients)) file.writeText(Json.encodeToString(clients))
@@ -121,9 +139,9 @@ class DhizukuActivity : ComponentActivity() {
confirmButton = { confirmButton = {
var time by remember { mutableIntStateOf(3) } var time by remember { mutableIntStateOf(3) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
(1..3).forEach { for (i in 2 downTo 0) {
delay(1000) delay(1000)
time -= 1 time = i
} }
} }
TextButton({ TextButton({
@@ -152,10 +170,11 @@ class DhizukuActivity : ComponentActivity() {
} }
} }
val DhizukuPermissions = listOf("remote_transact", "remote_process", "user_service", "delegated_scopes", "other")
@Serializable @Serializable
data class DhizukuClientInfo( data class DhizukuClientInfo(
val uid: Int, val uid: Int,
val signature: String?, val signature: String?,
val allow: Boolean val permissions: List<String> = emptyList()
) )

View File

@@ -10,6 +10,8 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -35,6 +37,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
@@ -44,6 +47,7 @@ import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
@@ -55,10 +59,10 @@ 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.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TriStateCheckbox
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -72,11 +76,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
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.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.state.ToggleableState
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
@@ -84,6 +90,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ChoosePackageContract import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
import com.bintianqi.owndroid.DhizukuClientInfo import com.bintianqi.owndroid.DhizukuClientInfo
import com.bintianqi.owndroid.DhizukuPermissions
import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.IUserService import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.MyAdminComponent import com.bintianqi.owndroid.MyAdminComponent
@@ -509,7 +516,8 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
LaunchedEffect(enabled) { LaunchedEffect(enabled) {
if (enabled) { if (enabled) {
clients.clear() clients.clear()
clients.addAll(Json.decodeFromString<List<DhizukuClientInfo>>(file.readText())) val json = Json { ignoreUnknownKeys = true }
clients.addAll(json.decodeFromString<List<DhizukuClientInfo>>(file.readText()))
} }
} }
MyLazyScaffold(R.string.dhizuku_server, onNavigateUp) { MyLazyScaffold(R.string.dhizuku_server, onNavigateUp) {
@@ -524,10 +532,12 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
writeList() writeList()
} else { } else {
val info = pm.getApplicationInfo(name, 0) val info = pm.getApplicationInfo(name, 0)
var expand by remember { mutableStateOf(false) }
Card(
Modifier.fillMaxWidth().padding(HorizontalPadding, 8.dp)
) {
Row( Row(
Modifier Modifier.fillMaxWidth().padding(8.dp, 8.dp, 0.dp, 8.dp),
.fillMaxWidth()
.padding(HorizontalPadding, 8.dp),
Arrangement.SpaceBetween, Alignment.CenterVertically Arrangement.SpaceBetween, Alignment.CenterVertically
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@@ -537,16 +547,51 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
.padding(end = 16.dp) .padding(end = 16.dp)
.size(50.dp) .size(50.dp)
) )
Column {
Text(info.loadLabel(pm).toString(), style = typography.titleLarge) Text(info.loadLabel(pm).toString(), style = typography.titleLarge)
Text(name, Modifier.alpha(0.7F), style = typography.bodyMedium)
} }
Switch(client.allow, { }
clients[index] = client.copy(allow = it) val ts = when (DhizukuPermissions.filter { it !in client.permissions }.size) {
0 -> ToggleableState.On
DhizukuPermissions.size -> ToggleableState.Off
else -> ToggleableState.Indeterminate
}
Row(verticalAlignment = Alignment.CenterVertically) {
TriStateCheckbox(ts, {
clients[index] = when (ts) {
ToggleableState.On, ToggleableState.Indeterminate -> client.copy(permissions = emptyList())
ToggleableState.Off -> client.copy(permissions = DhizukuPermissions)
}
})
val degrees by animateFloatAsState(if(expand) 180F else 0F)
IconButton({ expand = !expand }) {
Icon(Icons.Default.ArrowDropDown, null, Modifier.rotate(degrees))
}
}
}
AnimatedVisibility(expand, Modifier.padding(8.dp, 0.dp, 8.dp, 8.dp)) {
Column {
mapOf(
"remote_transact" to "Remote transact", "remote_process" to "Remote process",
"user_service" to "User service", "delegated_scopes" to "Delegated scopes",
"other" to context.getString(R.string.other)
).forEach { (k, v) ->
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
Text(v)
Checkbox(k in client.permissions, {
val newPermissions = if (it) client.permissions.plus(k) else client.permissions.minus(k)
clients[index] = client.copy(permissions = newPermissions)
writeList() writeList()
}) })
} }
} }
} }
} }
}
}
}
}
} }
@Serializable object LockScreenInfo @Serializable object LockScreenInfo

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB