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.viewModels
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.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.LaunchedEffect
@@ -21,6 +27,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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 clients = Json.decodeFromString<List<DhizukuClientInfo>>(file.readText())
val signature = getPackageSignature(packageInfo)
val hasPermission = DhizukuClientInfo(callingUid, signature, true) in clients
Log.d(TAG, "UID $callingUid, PID $callingPid, has permission: $hasPermission")
val requiredPermission = when (func) {
"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
}
@@ -91,9 +106,12 @@ class DhizukuActivity : ComponentActivity() {
val label = appInfo.loadLabel(packageManager).toString()
fun close(grantPermission: Boolean) {
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 clientInfo = DhizukuClientInfo(uid, getPackageSignature(packageInfo), grantPermission)
val clientInfo = DhizukuClientInfo(
uid, getPackageSignature(packageInfo), if (grantPermission) DhizukuPermissions else emptyList()
)
if (index == -1) clients += clientInfo
else clients[index] = clientInfo
file.writeText(Json.encodeToString(clients))
@@ -121,9 +139,9 @@ class DhizukuActivity : ComponentActivity() {
confirmButton = {
var time by remember { mutableIntStateOf(3) }
LaunchedEffect(Unit) {
(1..3).forEach {
for (i in 2 downTo 0) {
delay(1000)
time -= 1
time = i
}
}
TextButton({
@@ -152,10 +170,11 @@ class DhizukuActivity : ComponentActivity() {
}
}
val DhizukuPermissions = listOf("remote_transact", "remote_process", "user_service", "delegated_scopes", "other")
@Serializable
data class DhizukuClientInfo(
val uid: Int,
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.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.automirrored.filled.KeyboardArrowRight
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.Close
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.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
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.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TriStateCheckbox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -72,11 +76,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
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.KeyboardType
import androidx.compose.ui.unit.dp
@@ -84,6 +90,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
import com.bintianqi.owndroid.DhizukuClientInfo
import com.bintianqi.owndroid.DhizukuPermissions
import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.MyAdminComponent
@@ -509,7 +516,8 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
LaunchedEffect(enabled) {
if (enabled) {
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) {
@@ -524,10 +532,12 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
writeList()
} else {
val info = pm.getApplicationInfo(name, 0)
var expand by remember { mutableStateOf(false) }
Card(
Modifier.fillMaxWidth().padding(HorizontalPadding, 8.dp)
) {
Row(
Modifier
.fillMaxWidth()
.padding(HorizontalPadding, 8.dp),
Modifier.fillMaxWidth().padding(8.dp, 8.dp, 0.dp, 8.dp),
Arrangement.SpaceBetween, Alignment.CenterVertically
) {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -537,10 +547,41 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
.padding(end = 16.dp)
.size(50.dp)
)
Column {
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()
})
}
@@ -548,6 +589,10 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
}
}
}
}
}
}
}
@Serializable object LockScreenInfo

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB