From 1d8178c96e1cd3f5bc2d114aae736d2b0bcd5a79 Mon Sep 17 00:00:00 2001 From: BinTianqi <1220958406@qq.com> Date: Fri, 5 Apr 2024 18:25:27 +0800 Subject: [PATCH] permission picker in AppManage --- Guide.md | 2 +- Readme.md | 1 - app/build.gradle.kts | 4 +- .../com/binbin/androidowner/MainActivity.kt | 1 + .../binbin/androidowner/PermissionPicker.kt | 96 +++++++++++++++++++ .../com/binbin/androidowner/PkgSelector.kt | 1 + .../androidowner/dpm/ApplicationManage.kt | 48 +++++++--- .../java/com/binbin/androidowner/dpm/DPM.kt | 2 + app/src/main/res/values/strings.xml | 28 ++++++ 9 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/binbin/androidowner/PermissionPicker.kt diff --git a/Guide.md b/Guide.md index 36f1166..270dab1 100644 --- a/Guide.md +++ b/Guide.md @@ -691,7 +691,7 @@ adb shell pm list permissions 从允许或拒绝改为由用户决定会保持当前的状态 -有一些权限无法修改,比如安装应用 +只能修改运行时权限(可以通过对话框授权的权限) 在API31或以上,Profile owner不能再修改传感器相关权限,如果能修改传感器相关权限,说明这个设备是完全受管理设备(Device owner) diff --git a/Readme.md b/Readme.md index 5b0857c..edafa28 100644 --- a/Readme.md +++ b/Readme.md @@ -31,7 +31,6 @@ _我正在为这个App取一个新的名字......_ ### 正在开发的功能 -- 应用管理:应用权限选择器(现在只能手动输入权限名称) - 用户管理:用户选择器(现在只能手动输入用户序列号) - 安全日志和网络日志 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0694f43..6239a20 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.binbin.androidowner" minSdk = 21 targetSdk = 34 - versionCode = 22 - versionName = "4.5" + versionCode = 23 + versionName = "4.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index ad78a86..e775f55 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -108,6 +108,7 @@ fun MyScaffold(){ composable(route = "AppSetting", content = { AppSetting(navCtrl)}) composable(route = "Network", content = {Network(navCtrl)}) composable(route = "PackageSelector"){PackageSelector(navCtrl)} + composable(route = "PermissionPicker"){PermissionPicker(navCtrl)} } LaunchedEffect(Unit){ val profileInited = sharedPref.getBoolean("ManagedProfileActivated",false) diff --git a/app/src/main/java/com/binbin/androidowner/PermissionPicker.kt b/app/src/main/java/com/binbin/androidowner/PermissionPicker.kt new file mode 100644 index 0000000..7b74bec --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/PermissionPicker.kt @@ -0,0 +1,96 @@ +package com.binbin.androidowner + +import android.Manifest +import android.os.Build.VERSION +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.binbin.androidowner.dpm.applySelectedPermission +import com.binbin.androidowner.dpm.selectedPermission +import com.binbin.androidowner.ui.NavIcon +import com.binbin.androidowner.ui.theme.bgColor + +@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 = bgColor) + ) + } + ){ paddingValues-> + LazyColumn( + modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding()).background(bgColor) + ){ + items(permissionList()){ + Column( + modifier = Modifier + .fillMaxWidth() + .clickable{ + selectedPermission = it.first + applySelectedPermission = true + navCtrl.navigateUp() + } + .padding(vertical = 6.dp, horizontal = 8.dp) + ){ + Text(text = it.first) + Text(text = stringResource(it.second), modifier = Modifier.alpha(0.8F)) + } + } + items(1){ Spacer(Modifier.padding(vertical = 30.dp)) } + } + } +} + +private fun permissionList():List>{ + val list = mutableListOf>() + list.add(Pair(Manifest.permission.READ_EXTERNAL_STORAGE,R.string.permission_READ_EXTERNAL_STORAGE)) + list.add(Pair(Manifest.permission.WRITE_EXTERNAL_STORAGE,R.string.permission_WRITE_EXTERNAL_STORAGE)) + if(VERSION.SDK_INT>=33){ + list.add(Pair(Manifest.permission.READ_MEDIA_AUDIO,R.string.permission_READ_MEDIA_AUDIO)) + list.add(Pair(Manifest.permission.READ_MEDIA_VIDEO,R.string.permission_READ_MEDIA_VIDEO)) + list.add(Pair(Manifest.permission.READ_MEDIA_IMAGES,R.string.permission_READ_MEDIA_IMAGES)) + } + list.add(Pair(Manifest.permission.CAMERA,R.string.permission_CAMERA)) + list.add(Pair(Manifest.permission.RECORD_AUDIO,R.string.permission_RECORD_AUDIO)) + list.add(Pair(Manifest.permission.ACCESS_COARSE_LOCATION,R.string.permission_ACCESS_COARSE_LOCATION)) + list.add(Pair(Manifest.permission.ACCESS_FINE_LOCATION,R.string.permission_ACCESS_FINE_LOCATION)) + if(VERSION.SDK_INT>=29){ + list.add(Pair(Manifest.permission.ACCESS_BACKGROUND_LOCATION,R.string.permission_ACCESS_BACKGROUND_LOCATION)) + } + list.add(Pair(Manifest.permission.READ_CONTACTS,R.string.permission_READ_CONTACTS)) + list.add(Pair(Manifest.permission.WRITE_CONTACTS,R.string.permission_WRITE_CONTACTS)) + list.add(Pair(Manifest.permission.READ_CALENDAR,R.string.permission_READ_CALENDAR)) + list.add(Pair(Manifest.permission.WRITE_CALENDAR,R.string.permission_WRITE_CALENDAR)) + list.add(Pair(Manifest.permission.CALL_PHONE,R.string.permission_CALL_PHONE)) + list.add(Pair(Manifest.permission.READ_PHONE_STATE,R.string.permission_READ_PHONE_STATE)) + list.add(Pair(Manifest.permission.READ_SMS,R.string.permission_READ_SMS)) + list.add(Pair(Manifest.permission.RECEIVE_SMS,R.string.permission_RECEIVE_SMS)) + list.add(Pair(Manifest.permission.SEND_SMS,R.string.permission_SEND_SMS)) + list.add(Pair(Manifest.permission.READ_CALL_LOG,R.string.permission_READ_CALL_LOG)) + list.add(Pair(Manifest.permission.WRITE_CALL_LOG,R.string.permission_WRITE_CALL_LOG)) + list.add(Pair(Manifest.permission.BODY_SENSORS,R.string.permission_BODY_SENSORS)) + if(VERSION.SDK_INT>=33){ + list.add(Pair(Manifest.permission.BODY_SENSORS_BACKGROUND,R.string.permission_BODY_SENSORS_BACKGROUND)) + } + if(VERSION.SDK_INT>29){ + list.add(Pair(Manifest.permission.ACTIVITY_RECOGNITION,R.string.permission_ACTIVITY_RECOGNITION)) + } + if(VERSION.SDK_INT>=33){ + list.add(Pair(Manifest.permission.POST_NOTIFICATIONS,R.string.permission_POST_NOTIFICATIONS)) + } + //list.add(Pair(Manifest.permission.,R.string.)) + return list +} diff --git a/app/src/main/java/com/binbin/androidowner/PkgSelector.kt b/app/src/main/java/com/binbin/androidowner/PkgSelector.kt index b8cf7f2..ff86e8e 100644 --- a/app/src/main/java/com/binbin/androidowner/PkgSelector.kt +++ b/app/src/main/java/com/binbin/androidowner/PkgSelector.kt @@ -152,6 +152,7 @@ fun PackageSelector(navCtrl:NavHostController){ PackageItem(it, navCtrl) } } + items(1){Spacer(Modifier.padding(vertical = 30.dp))} }else{ items(1){ Spacer(Modifier.padding(top = 5.dp)) diff --git a/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt index f1ce02d..a057f5d 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt @@ -64,6 +64,7 @@ private var crossProfilePkg = mutableSetOf() private var keepUninstallPkg = mutableListOf() private var permittedIme = mutableListOf() private var permittedAccessibility = mutableListOf() + @Composable fun ApplicationManage(navCtrl:NavHostController){ val focusMgr = LocalFocusManager.current @@ -98,8 +99,8 @@ fun ApplicationManage(navCtrl:NavHostController){ Column(modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())){ LaunchedEffect(Unit) { while(true){ - if(applySelectedPackage){pkgName = selectedPackage; applySelectedPackage = false} - delay(200) + if(applySelectedPackage){ pkgName = selectedPackage; applySelectedPackage = false; applySelectedPermission = true} + delay(100) } } if(backStackEntry?.destination?.route!="InstallApp"){ @@ -131,7 +132,7 @@ fun ApplicationManage(navCtrl:NavHostController){ composable(route = "Home"){Home(localNavCtrl,pkgName)} composable(route = "BlockUninstall"){BlockUninstall(pkgName)} composable(route = "UserControlDisabled"){UserCtrlDisabledPkg(pkgName)} - composable(route = "PermissionManage"){PermissionManage(pkgName)} + composable(route = "PermissionManage"){PermissionManage(pkgName,navCtrl)} composable(route = "CrossProfilePackage"){CrossProfilePkg(pkgName)} composable(route = "CrossProfileWidget"){CrossProfileWidget(pkgName)} composable(route = "CredentialManagePolicy"){CredentialManagePolicy(pkgName)} @@ -155,8 +156,7 @@ private fun Home(navCtrl:NavHostController, pkgName: String){ val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Text(text = stringResource(R.string.scope_is_work_profile), textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) - Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.scope_is_work_profile), textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth()) } SubPageItem(R.string.app_info,"",R.drawable.open_in_new){ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) @@ -343,38 +343,57 @@ private fun BlockUninstall(pkgName: String){ @SuppressLint("NewApi") @Composable -private fun PermissionManage(pkgName: String){ +private fun PermissionManage(pkgName: String, navCtrl: NavHostController){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current + var inputPermission by remember{mutableStateOf(selectedPermission)} + var currentState by remember{mutableStateOf(myContext.getString(R.string.unknown))} val grantState = mapOf( PERMISSION_GRANT_STATE_DEFAULT to stringResource(R.string.decide_by_user), PERMISSION_GRANT_STATE_GRANTED to stringResource(R.string.granted), PERMISSION_GRANT_STATE_DENIED to stringResource(R.string.denied) ) + LaunchedEffect(Unit) { + while(true){ + if(applySelectedPermission){inputPermission = selectedPermission; applySelectedPermission = false} + delay(100) + } + } + LaunchedEffect(pkgName) { + if(pkgName!=""){currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!!} + } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ - var inputPermission by remember{mutableStateOf("android.permission.")} - var currentState by remember{mutableStateOf(grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)])} 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}, + onValueChange = { + inputPermission = it; selectedPermission = inputPermission + currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!! + }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth() + modifier = Modifier.focusable().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?:stringResource(R.string.unknown))) + Text(stringResource(R.string.current_state, currentState)) Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_GRANTED) - currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] + currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!! }, modifier = Modifier.fillMaxWidth(0.49F) ) { @@ -383,7 +402,7 @@ private fun PermissionManage(pkgName: String){ Button( onClick = { myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DENIED) - currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] + currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!! }, Modifier.fillMaxWidth(0.96F) ) { @@ -393,7 +412,7 @@ private fun PermissionManage(pkgName: String){ Button( onClick = { myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DEFAULT) - currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] + currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!! }, modifier = Modifier.fillMaxWidth() ) { @@ -410,6 +429,7 @@ private fun CrossProfilePkg(pkgName: String){ val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.cross_profile_package), style = typography.headlineLarge) var list by remember{mutableStateOf("")} val refresh = { diff --git a/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt b/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt index 3ad10a6..fa3df3a 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt @@ -7,6 +7,8 @@ import androidx.activity.result.ActivityResultLauncher var selectedPackage = "" var applySelectedPackage = false +var selectedPermission = "" +var applySelectedPermission = false lateinit var getCaCert: ActivityResultLauncher lateinit var createManagedProfile: ActivityResultLauncher lateinit var getApk: ActivityResultLauncher diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 01f53d7..b37da39 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -244,6 +244,7 @@ 显示系统应用 显示priv-app 显示apex应用 + 权限选择器 挂起 隐藏 如果隐藏,有可能是没安装 @@ -472,4 +473,31 @@ 需要打开夜间模式 需要重启应用 + + 读取外部存储 + 写入外部存储 + 读取音频 + 读取视频 + 读取图片 + 相机 + 录音 + 读取联系人 + 写入联系人 + 读取日历 + 写入日历 + 粗略位置 + 准确位置 + 后台获取位置 + 打电话 + 读取手机状态 + 读取短信 + 接收短信 + 发送短信 + 读取通话记录 + 写入通话记录 + 传感器 + 后台使用传感器 + 查看使用情况 + 发送通知 +