App manager: Permission manage: use dialog to set permission grant status, remove permission picker

close #64
This commit is contained in:
BinTianqi
2024-07-24 11:20:05 +08:00
parent 2bb045e952
commit a5d5e69a15
5 changed files with 141 additions and 187 deletions

View File

@@ -162,7 +162,6 @@ fun Home(materialYou:MutableState<Boolean>, blackTheme:MutableState<Boolean>) {
composable(route = "AppSetting") { AppSetting(navCtrl, materialYou, blackTheme) }
composable(route = "Network") { Network(navCtrl) }
composable(route = "PackageSelector") { PackageSelector(navCtrl, pkgName) }
composable(route = "PermissionPicker") { PermissionPicker(navCtrl) }
}
LaunchedEffect(Unit) {
val profileInited = sharedPref.getBoolean("ManagedProfileActivated", false)

View File

@@ -1,120 +0,0 @@
package com.bintianqi.owndroid
import android.Manifest
import android.os.Build.VERSION
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.dpm.selectedPermission
import com.bintianqi.owndroid.ui.NavIcon
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PermissionPicker(navCtrl: NavHostController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = stringResource(R.string.permission_picker)) },
navigationIcon = { NavIcon{ navCtrl.navigateUp() } },
colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.background)
)
}
) { paddingValues->
LazyColumn(
modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())
) {
items(permissionList()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable{
selectedPermission.value = it.permission
navCtrl.navigateUp()
}
.padding(vertical = 8.dp, horizontal = 8.dp)
) {
Icon(
painter = painterResource(it.icon),
contentDescription = stringResource(it.label),
modifier = Modifier.padding(start = 8.dp, end = 10.dp)
)
Column {
Text(text = stringResource(it.label))
Text(text = it.permission, modifier = Modifier.alpha(0.8F), style = MaterialTheme.typography.bodyMedium)
}
}
}
items(1) { Spacer(Modifier.padding(vertical = 30.dp)) }
}
}
}
private data class PermissionPickerItem(
val permission: String,
@StringRes val label: Int,
@DrawableRes val icon: Int
)
private fun permissionList(): List<PermissionPickerItem>{
val list = mutableListOf<PermissionPickerItem>()
list.add(PermissionPickerItem(Manifest.permission.READ_EXTERNAL_STORAGE, R.string.permission_READ_EXTERNAL_STORAGE, R.drawable.folder_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_EXTERNAL_STORAGE, R.string.permission_WRITE_EXTERNAL_STORAGE, R.drawable.folder_fill0))
if(VERSION.SDK_INT >= 33) {
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_AUDIO, R.string.permission_READ_MEDIA_AUDIO, R.drawable.music_note_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_VIDEO, R.string.permission_READ_MEDIA_VIDEO, R.drawable.movie_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_IMAGES, R.string.permission_READ_MEDIA_IMAGES, R.drawable.image_fill0))
}
list.add(PermissionPickerItem(Manifest.permission.CAMERA, R.string.permission_CAMERA, R.drawable.photo_camera_fill0))
list.add(PermissionPickerItem(Manifest.permission.RECORD_AUDIO, R.string.permission_RECORD_AUDIO, R.drawable.mic_fill0))
list.add(PermissionPickerItem(Manifest.permission.ACCESS_COARSE_LOCATION, R.string.permission_ACCESS_COARSE_LOCATION, R.drawable.location_on_fill0))
list.add(PermissionPickerItem(Manifest.permission.ACCESS_FINE_LOCATION, R.string.permission_ACCESS_FINE_LOCATION, R.drawable.location_on_fill0))
if(VERSION.SDK_INT >= 29) {
list.add(PermissionPickerItem(Manifest.permission.ACCESS_BACKGROUND_LOCATION, R.string.permission_ACCESS_BACKGROUND_LOCATION, R.drawable.location_on_fill0))
}
list.add(PermissionPickerItem(Manifest.permission.READ_CONTACTS, R.string.permission_READ_CONTACTS, R.drawable.contacts_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CONTACTS, R.string.permission_WRITE_CONTACTS, R.drawable.contacts_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_CALENDAR, R.string.permission_READ_CALENDAR, R.drawable.calendar_month_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CALENDAR, R.string.permission_WRITE_CALENDAR, R.drawable.calendar_month_fill0))
list.add(PermissionPickerItem(Manifest.permission.CALL_PHONE, R.string.permission_CALL_PHONE, R.drawable.call_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_PHONE_STATE, R.string.permission_READ_PHONE_STATE, R.drawable.mobile_phone_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_SMS, R.string.permission_READ_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.RECEIVE_SMS, R.string.permission_RECEIVE_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.SEND_SMS, R.string.permission_SEND_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_CALL_LOG, R.string.permission_READ_CALL_LOG, R.drawable.call_log_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CALL_LOG, R.string.permission_WRITE_CALL_LOG, R.drawable.call_log_fill0))
list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS, R.string.permission_BODY_SENSORS, R.drawable.sensors_fill0))
if(VERSION.SDK_INT >= 33) {
list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS_BACKGROUND, R.string.permission_BODY_SENSORS_BACKGROUND, R.drawable.sensors_fill0))
}
if(VERSION.SDK_INT > 29) {
list.add(PermissionPickerItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0))
}
if(VERSION.SDK_INT >= 33) {
list.add(PermissionPickerItem(Manifest.permission.POST_NOTIFICATIONS, R.string.permission_POST_NOTIFICATIONS, R.drawable.notifications_fill0))
}
return list
}

View File

@@ -19,6 +19,7 @@ import android.provider.Settings
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
@@ -41,7 +42,6 @@ import androidx.compose.material3.Card
import androidx.compose.material3.Icon
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
@@ -55,12 +55,15 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
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.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
@@ -157,7 +160,7 @@ fun ApplicationManage(navCtrl:NavHostController, pkgName: MutableState<String>,
}
composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(pkgName.value) }
composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName.value) }
composable(route = "PermissionManage") { PermissionManage(pkgName.value, navCtrl) }
composable(route = "PermissionManage") { PermissionManage(pkgName.value) }
composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName.value) }
composable(route = "CrossProfileWidget") { CrossProfileWidget(pkgName.value) }
composable(route = "CredentialManagePolicy") { CredentialManagePolicy(pkgName.value) }
@@ -399,84 +402,98 @@ private fun UserCtrlDisabledPkg(pkgName:String) {
@SuppressLint("NewApi")
@Composable
private fun PermissionManage(pkgName: String, navCtrl: NavHostController) {
private fun PermissionManage(pkgName: String) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var inputPermission by remember { mutableStateOf("") }
var currentState by remember { mutableStateOf(context.getString(R.string.unknown)) }
var showDialog by remember { mutableStateOf(false) }
var selectedPermission by remember { mutableStateOf(PermissionPickerItem("", R.string.unknown, R.drawable.block_fill0)) }
val statusMap = remember { mutableStateMapOf<String, Int>() }
val grantState = mapOf(
PERMISSION_GRANT_STATE_DEFAULT to stringResource(R.string.default_stringres),
PERMISSION_GRANT_STATE_GRANTED to stringResource(R.string.granted),
PERMISSION_GRANT_STATE_DENIED to stringResource(R.string.denied)
)
val applyPermission by selectedPermission.collectAsState()
LaunchedEffect(applyPermission) {
if(applyPermission != "") {
inputPermission = applyPermission
selectedPermission.value = ""
}
}
LaunchedEffect(pkgName) {
if(pkgName!="") { currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!! }
if(pkgName != "") {
permissionList().forEach { statusMap[it.permission] = dpm.getPermissionGrantState(receiver, pkgName, it.permission) }
}
}
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.permission_manage), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = inputPermission,
label = { Text(stringResource(R.string.permission)) },
onValueChange = {
inputPermission = it
currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!!
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth(),
trailingIcon = {
Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = { navCtrl.navigate("PermissionPicker") })
.padding(3.dp))
}
)
Spacer(Modifier.padding(vertical = 5.dp))
Text(stringResource(R.string.current_state, currentState))
Spacer(Modifier.padding(vertical = 5.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
dpm.setPermissionGrantState(receiver,pkgName,inputPermission, PERMISSION_GRANT_STATE_GRANTED)
currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!!
},
modifier = Modifier.fillMaxWidth(0.49F)
Spacer(Modifier.padding(vertical = 4.dp))
for(permission in permissionList()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable {
if(pkgName != "") {
selectedPermission = permission
showDialog = true
}
}
.padding(top = 8.dp, bottom = 8.dp, end = 8.dp)
) {
Text(stringResource(R.string.grant))
Icon(
painter = painterResource(permission.icon),
contentDescription = stringResource(permission.label),
modifier = Modifier.padding(horizontal = 12.dp)
)
Column {
Text(text = stringResource(permission.label))
Text(
text = grantState[statusMap[permission.permission]]?: stringResource(R.string.unknown),
modifier = Modifier.alpha(0.7F), style = typography.bodyMedium
)
}
}
Button(
onClick = {
dpm.setPermissionGrantState(receiver,pkgName,inputPermission, PERMISSION_GRANT_STATE_DENIED)
currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!!
},
Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.deny))
}
}
Button(
onClick = {
dpm.setPermissionGrantState(receiver,pkgName,inputPermission, PERMISSION_GRANT_STATE_DEFAULT)
currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!!
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.default_stringres))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
if(showDialog) {
val grantPermission: (Int)->Unit = {
dpm.setPermissionGrantState(receiver, pkgName, selectedPermission.permission, it)
statusMap[selectedPermission.permission] = dpm.getPermissionGrantState(receiver, pkgName, selectedPermission.permission)
showDialog = false
}
@Composable
fun GrantPermissionItem(label: Int, status: Int) {
val selected = statusMap[selectedPermission.permission] == status
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(if(selected) colorScheme.primaryContainer else Color.Transparent)
.clickable { grantPermission(status) }
.padding(vertical = 16.dp, horizontal = 12.dp)
) {
Text(text = stringResource(label), color = if(selected) colorScheme.primary else Color.Unspecified)
if(selected) {
Icon(
painter = painterResource(R.drawable.check_circle_fill0),
contentDescription = stringResource(label),
tint = colorScheme.primary
)
}
}
}
AlertDialog(
onDismissRequest = { showDialog = false },
confirmButton = { TextButton(onClick = { showDialog = false }) { Text(stringResource(R.string.cancel)) } },
title = { Text(stringResource(selectedPermission.label)) },
text = {
Column {
Text(selectedPermission.permission)
Spacer(Modifier.padding(vertical = 4.dp))
GrantPermissionItem(R.string.grant, PERMISSION_GRANT_STATE_GRANTED)
GrantPermissionItem(R.string.deny, PERMISSION_GRANT_STATE_DENIED)
GrantPermissionItem(R.string.default_stringres, PERMISSION_GRANT_STATE_DEFAULT)
}
}
)
}
}
@SuppressLint("NewApi")

View File

@@ -1,12 +1,12 @@
package com.bintianqi.owndroid.dpm
import android.Manifest
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.app.admin.FactoryResetProtectionPolicy
import android.app.admin.IDevicePolicyManager
import android.app.admin.SystemUpdatePolicy
import android.app.admin.WifiSsidPolicy
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -15,7 +15,10 @@ import android.os.Build.VERSION
import androidx.activity.ComponentActivity.CONTEXT_IGNORE_SECURITY
import androidx.activity.ComponentActivity.DEVICE_POLICY_SERVICE
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.bintianqi.owndroid.PackageInstallerReceiver
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.backToHomeStateFlow
import com.rosan.dhizuku.api.Dhizuku
@@ -25,7 +28,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import java.io.IOException
import java.io.InputStream
var selectedPermission = MutableStateFlow("")
lateinit var createManagedProfile: ActivityResultLauncher<Intent>
lateinit var addDeviceAdmin: ActivityResultLauncher<Intent>
@@ -194,3 +196,50 @@ fun Context.resetDevicePolicy() {
}
dpm.setRecommendedGlobalProxy(receiver, null)
}
data class PermissionPickerItem(
val permission: String,
@StringRes val label: Int,
@DrawableRes val icon: Int
)
fun permissionList(): List<PermissionPickerItem>{
val list = mutableListOf<PermissionPickerItem>()
list.add(PermissionPickerItem(Manifest.permission.READ_EXTERNAL_STORAGE, R.string.permission_READ_EXTERNAL_STORAGE, R.drawable.folder_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_EXTERNAL_STORAGE, R.string.permission_WRITE_EXTERNAL_STORAGE, R.drawable.folder_fill0))
if(VERSION.SDK_INT >= 33) {
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_AUDIO, R.string.permission_READ_MEDIA_AUDIO, R.drawable.music_note_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_VIDEO, R.string.permission_READ_MEDIA_VIDEO, R.drawable.movie_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_IMAGES, R.string.permission_READ_MEDIA_IMAGES, R.drawable.image_fill0))
}
list.add(PermissionPickerItem(Manifest.permission.CAMERA, R.string.permission_CAMERA, R.drawable.photo_camera_fill0))
list.add(PermissionPickerItem(Manifest.permission.RECORD_AUDIO, R.string.permission_RECORD_AUDIO, R.drawable.mic_fill0))
list.add(PermissionPickerItem(Manifest.permission.ACCESS_COARSE_LOCATION, R.string.permission_ACCESS_COARSE_LOCATION, R.drawable.location_on_fill0))
list.add(PermissionPickerItem(Manifest.permission.ACCESS_FINE_LOCATION, R.string.permission_ACCESS_FINE_LOCATION, R.drawable.location_on_fill0))
if(VERSION.SDK_INT >= 29) {
list.add(PermissionPickerItem(Manifest.permission.ACCESS_BACKGROUND_LOCATION, R.string.permission_ACCESS_BACKGROUND_LOCATION, R.drawable.location_on_fill0))
}
list.add(PermissionPickerItem(Manifest.permission.READ_CONTACTS, R.string.permission_READ_CONTACTS, R.drawable.contacts_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CONTACTS, R.string.permission_WRITE_CONTACTS, R.drawable.contacts_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_CALENDAR, R.string.permission_READ_CALENDAR, R.drawable.calendar_month_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CALENDAR, R.string.permission_WRITE_CALENDAR, R.drawable.calendar_month_fill0))
list.add(PermissionPickerItem(Manifest.permission.CALL_PHONE, R.string.permission_CALL_PHONE, R.drawable.call_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_PHONE_STATE, R.string.permission_READ_PHONE_STATE, R.drawable.mobile_phone_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_SMS, R.string.permission_READ_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.RECEIVE_SMS, R.string.permission_RECEIVE_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.SEND_SMS, R.string.permission_SEND_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_CALL_LOG, R.string.permission_READ_CALL_LOG, R.drawable.call_log_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CALL_LOG, R.string.permission_WRITE_CALL_LOG, R.drawable.call_log_fill0))
list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS, R.string.permission_BODY_SENSORS, R.drawable.sensors_fill0))
if(VERSION.SDK_INT >= 33) {
list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS_BACKGROUND, R.string.permission_BODY_SENSORS_BACKGROUND, R.drawable.sensors_fill0))
}
if(VERSION.SDK_INT > 29) {
list.add(PermissionPickerItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0))
}
if(VERSION.SDK_INT >= 33) {
list.add(PermissionPickerItem(Manifest.permission.POST_NOTIFICATIONS, R.string.permission_POST_NOTIFICATIONS, R.drawable.notifications_fill0))
}
return list
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m424,664 l282,-282 -56,-56 -226,226 -114,-114 -56,56 170,170ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z"
android:fillColor="#000000"/>
</vector>