平台
Android11 + RK3566 + AndroidStudio
Android 权限的变化, 几乎每个版本的SDK都会有, 其中最大的一次是在6.0时, 增加的动态权限申请
读写存储的权限也几经更迭, 对开发人员来说, 越来越难.比如, 本文所要讨论的:允许管理所有文件
如何出现上面两种不同的文件权限选项?
1.首先是 targetSdkVersion 大于等于 30. (build.gradle)
2.当声明了 READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE
仅允许访问媒体文件
3.当声明了 MANAGE_EXTERNAL_STORAGE 会增加允许管理所有文件
4.targetSdkVersion <= 28 时, 只有允许管理所有文件和 拒绝 选项.
编写测试代码执行以下动作:
1.申请权限
2.获取内部存储下的1.txt文件
3.若文件存在, 删除并输出结果
4.尝试写入文件
5.读写失败:
2022-09-03 07:25:11.067 1262-10770/com.android.providers.media.module E/MediaProvider: insertFileIfNecessary failed java.lang.IllegalArgumentException: Primary directory null not allowed for content://media/external_primary/file; allowed directories are [Download, Documents] at com.android.providers.media.MediaProvider.ensureFileColumns(MediaProvider.java:2923) at com.android.providers.media.MediaProvider.ensureUniqueFileColumns(MediaProvider.java:2588) at com.android.providers.media.MediaProvider.insertFile(MediaProvider.java:3282) at com.android.providers.media.MediaProvider.insertInternal(MediaProvider.java:3826) at com.android.providers.media.MediaProvider.insert(MediaProvider.java:3537) at com.android.providers.media.MediaProvider.insertFileForFuse(MediaProvider.java:7187) at com.android.providers.media.MediaProvider.insertFileIfNecessaryForFuse(MediaProvider.java:7281) 2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err: java.io.FileNotFoundException: /storage/emulated/0/1.txt: open failed: EPERM (Operation not permitted) 2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err: at libcore.io.IoBridge.open(IoBridge.java:492) 2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err: at java.io.FileOutputStream.<init>(FileOutputStream.java:236) 2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err: at java.io.FileOutputStream.<init>(FileOutputStream.java:186) 2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err: at com.android.apitester.PermissionTest.fileOperation(PermissionTest.java:124) 2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err: at com.android.apitester.PermissionTest.onClick(PermissionTest.java:50) 2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err: at android.view.View.performClick(View.java:7448) 2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err: at android.view.View.performClickInternal(View.java:7425) 2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err: at android.view.View.access$3600(View.java:810) 2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err: at android.view.View$PerformClick.run(View.java:28310) 2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err: at android.os.Handler.handleCallback(Handler.java:938) 2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err: at android.os.Handler.dispatchMessage(Handler.java:99) 2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err: at android.os.Looper.loop(Looper.java:223) 2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err: at android.app.ActivityThread.main(ActivityThread.java:7664) 2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err: at java.lang.reflect.Method.invoke(Native Method) 2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err: Caused by: android.system.ErrnoException: open failed: EPERM (Operation not permitted) 2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err: at libcore.io.Linux.open(Native Method) 2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err: at libcore.io.ForwardingOs.open(ForwardingOs.java:166) 2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err: at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254) 2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err: at libcore.io.ForwardingOs.open(ForwardingOs.java:166) 2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err: at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7550) 2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err: at libcore.io.IoBridge.open(IoBridge.java:478) 2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err: ... 15 more 2022-09-03 07:25:11.073 10710-10710/com.android.apitester E/PermissionTest: write /storage/emulated/0/1.txt failed 2022-09-03 07:25:11.094 1262-1367/com.android.providers.media.module I/MediaProvider: Deleted 1 items on external_primary due to com.android.apitester 2022-09-03 07:25:11.097 10710-10710/com.android.apitester D/PermissionTest: delete /storage/emulated/0/Download/1.txt success 2022-09-03 07:25:11.124 10710-10710/com.android.apitester D/PermissionTest: write /storage/emulated/0/Download/1.txt success 2022-09-03 07:25:11.131 10710-10710/com.android.apitester D/PermissionTest: delete /storage/emulated/0/Android/data/com.android.apitester/files/Documents/1.txt success 2022-09-03 07:25:11.137 10710-10710/com.android.apitester D/PermissionTest: write /storage/emulated/0/Android/data/com.android.apitester/files/Documents/1.txt success
结果(FAILED:失败, SUCCESS成功):
源码中权限窗口
packages/apps/PermissionController/
START u0 {act=android.intent.action.MANAGE_APP_PERMISSIONS cmp=com.android.permissioncontroller/.permission.ui.ManagePermissionsActivity (has extras)} from uid 1000
布局文件
packages/apps/PermissionController/res/navigation/nav_graph.xml
packages/apps/PermissionController/res/layout/app_permission.xml
相关源码
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
请求权限的交互
UI显示内容的判定
加载应用存储权限
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
data class FullStoragePackageState( val packageName: String, val user: UserHandle, val isLegacy: Boolean, val isGranted: Boolean ) override suspend fun loadDataAndPostValue(job: Job) { val storagePackages = standardPermGroupsPackagesLiveData.value?.get(STORAGE) ?: return val appOpsManager = app.getSystemService(AppOpsManager::class.java) ?: return val fullStoragePackages = mutableListOf<FullStoragePackageState>() for ((user, packageInfoList) in AllPackageInfosLiveData.value ?: emptyMap()) { val userPackages = packageInfoList.filter { storagePackages.contains(it.packageName to user) || it.requestedPermissions.contains(MANAGE_EXTERNAL_STORAGE) } for (packageInfo in userPackages) { val sdk = packageInfo.targetSdkVersion if (sdk < Build.VERSION_CODES.P) {//targetSdkVersion 28 fullStoragePackages.add(FullStoragePackageState(packageInfo.packageName, user, isLegacy = true, isGranted = true)) continue } else if (sdk <= Build.VERSION_CODES.Q &&//targetSdkVersion 29 appOpsManager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, packageInfo.uid, packageInfo.packageName) == MODE_ALLOWED) { fullStoragePackages.add(FullStoragePackageState(packageInfo.packageName, user, isLegacy = true, isGranted = true)) continue } //存在MANAGE_EXTERNAL_STORAGE if (MANAGE_EXTERNAL_STORAGE in packageInfo.requestedPermissions) { val mode = appOpsManager.unsafeCheckOpNoThrow(OPSTR_MANAGE_EXTERNAL_STORAGE, packageInfo.uid, packageInfo.packageName) val granted = mode == MODE_ALLOWED || mode == MODE_FOREGROUND || (mode == MODE_DEFAULT && MANAGE_EXTERNAL_STORAGE in packageInfo.grantedPermissions) fullStoragePackages.add(FullStoragePackageState(packageInfo.packageName, user, isLegacy = false, isGranted = granted)) } } } postValue(fullStoragePackages) }
isLegacy表示是否是旧的权限模式, UI会根据上面的代码进行逻辑运算并更新对应的UI信息.
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
override fun onUpdate() { val group = appPermGroupLiveData.value ?: return val admin = RestrictedLockUtils.getProfileOrDeviceOwner(app, user) val couldPackageHaveFgCapabilities = foregroundCapableType != Utils.ForegroundCapableType.NONE val allowedState = ButtonState() val allowedAlwaysState = ButtonState() val allowedForegroundState = ButtonState() val askOneTimeState = ButtonState() val askState = ButtonState() val deniedState = ButtonState() val deniedForegroundState = ButtonState() // when bg is fixed as granted and fg is flex askState.isShown = Utils.supportsOneTimeGrant(permGroupName) && !(group.foreground.isGranted && group.isOneTime) deniedState.isShown = true if (group.hasPermWithBackgroundMode) { // Background / Foreground / Deny case allowedForegroundState.isShown = true if (group.hasBackgroundGroup) { allowedAlwaysState.isShown = true } allowedAlwaysState.isChecked = group.background.isGranted && group.foreground.isGranted allowedForegroundState.isChecked = group.foreground.isGranted && !group.background.isGranted && !group.isOneTime askState.isChecked = !group.foreground.isGranted && group.isOneTime askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime askOneTimeState.isShown = askOneTimeState.isChecked deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime var detailId = 0 if (applyFixToForegroundBackground(group, group.foreground.isSystemFixed, group.background.isSystemFixed, allowedAlwaysState, allowedForegroundState, askState, deniedState, deniedForegroundState) || applyFixToForegroundBackground(group, group.foreground.isPolicyFixed, group.background.isPolicyFixed, allowedAlwaysState, allowedForegroundState, askState, deniedState, deniedForegroundState)) { showAdminSupportLiveData.value = admin detailId = getDetailResIdForFixedByPolicyPermissionGroup(group, admin != null) if (detailId != 0) { detailResIdLiveData.value = detailId to null } } else if (Utils.areGroupPermissionsIndividuallyControlled(app, permGroupName)) { val detailPair = getIndividualPermissionDetailResId(group) detailId = detailPair.first detailResIdLiveData.value = detailId to detailPair.second } if (couldPackageHaveFgCapabilities) { // Correct the UI in case the app can access bg location with only fg perm allowedAlwaysState.isShown = true allowedAlwaysState.isChecked = allowedAlwaysState.isChecked || allowedForegroundState.isChecked // Should be enabled && is denied enabled for the user to be able to switch to. allowedAlwaysState.isEnabled = ((allowedAlwaysState.isEnabled && allowedAlwaysState.isShown) || allowedForegroundState.isEnabled) && ((deniedState.isEnabled && deniedState.isShown) || (deniedForegroundState.isEnabled && deniedForegroundState.isShown)) allowedForegroundState.isChecked = false allowedForegroundState.isEnabled = false deniedState.isChecked = deniedState.isChecked || askState.isChecked deniedForegroundState.isChecked = deniedState.isChecked askState.isEnabled = false if (detailId == 0) { detailId = getForegroundCapableDetailResId(foregroundCapableType) if (detailId != 0) { detailResIdLiveData.value = detailId to null } } } } else { // Allow / Deny case allowedState.isShown = true allowedState.isChecked = group.foreground.isGranted askState.isChecked = !group.foreground.isGranted && group.isOneTime askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime askOneTimeState.isShown = askOneTimeState.isChecked deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime var detailId = 0 if (group.foreground.isPolicyFixed || group.foreground.isSystemFixed) { allowedState.isEnabled = false askState.isEnabled = false deniedState.isEnabled = false showAdminSupportLiveData.value = admin val detailId = getDetailResIdForFixedByPolicyPermissionGroup(group, admin != null) if (detailId != 0) { detailResIdLiveData.value = detailId to null } } if (isForegroundGroupSpecialCase(permGroupName)) { allowedForegroundState.isShown = true allowedState.isShown = false allowedForegroundState.isChecked = allowedState.isChecked allowedForegroundState.isEnabled = allowedState.isEnabled if (couldPackageHaveFgCapabilities || (Utils.isEmergencyApp(app, packageName) && isMicrophone(permGroupName))) { allowedAlwaysState.isShown = true allowedAlwaysState.isChecked = allowedForegroundState.isChecked allowedAlwaysState.isEnabled = allowedForegroundState.isEnabled allowedForegroundState.isChecked = false allowedForegroundState.isEnabled = false deniedState.isChecked = deniedState.isChecked || askState.isChecked askState.isEnabled = false if (detailId == 0) { detailId = getForegroundCapableDetailResId(foregroundCapableType) if (detailId != 0) { detailResIdLiveData.value = detailId to null } } } } } if (group.packageInfo.targetSdkVersion < Build.VERSION_CODES.M) { // Pre-M app's can't ask for runtime permissions askState.isShown = false deniedState.isChecked = askState.isChecked || deniedState.isChecked deniedForegroundState.isChecked = askState.isChecked || deniedForegroundState.isChecked } val storageState = fullStorageStateLiveData.value if (isStorage && storageState?.isLegacy != true) { val allowedAllFilesState = allowedAlwaysState val allowedMediaOnlyState = allowedForegroundState if (storageState != null) { // Set up the tri state permission for storage allowedAllFilesState.isEnabled = allowedState.isEnabled allowedAllFilesState.isShown = true if (storageState.isGranted) { allowedAllFilesState.isChecked = true deniedState.isChecked = false } } else { allowedAllFilesState.isEnabled = false allowedAllFilesState.isShown = false } allowedMediaOnlyState.isShown = true allowedMediaOnlyState.isEnabled = allowedState.isEnabled allowedMediaOnlyState.isChecked = allowedState.isChecked && storageState?.isGranted != true allowedState.isChecked = false allowedState.isShown = false } value = mapOf(ALLOW to allowedState, ALLOW_ALWAYS to allowedAlwaysState, ALLOW_FOREGROUND to allowedForegroundState, ASK_ONCE to askOneTimeState, ASK to askState, DENY to deniedState, DENY_FOREGROUND to deniedForegroundState) } }
权限请求
当targetSdkVersion设置为高版本后, 下面的权限请求代码, 只能申请到仅允许访问媒体文件
String[] perms = { //"android.permission.MANAGE_EXTERNAL_STORAGE", Manifest.permission.MANAGE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, }; requestPermissions(perms, 0x01);
实际上, MANAGE_EXTERNAL_STORAGE现传统的读写权限有很大的区别, 它与浮窗的权限类似, 由AppOpsService进行管理, 上面的代码, 不是能直接向AppOpsService申请权限.
开发者可以借助三方工具实现权限请求一般会通过调起系统的授权窗口, 引导用户操作授权:
1.方法 一
设置 > 应用和通知 > 高级 特殊应用权限 > 所有文件访问权限 > App名称 > 授予所有文件管权限
2.方法 二 (实际去到了PermissionController)
设置 > 应用和通知 > 所有应用 > App名称 > 权限 > 文件和媒体 > 允许管理所有文件
//方法1 START u0 {act=android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION dat=package:com.android.apitester cmp=com.android.settings/.Settings$AppManageExternalStorageActivity} 方法2 START u0 {act=android.intent.action.MANAGE_APP_PERMISSIONS cmp=com.android.permissioncontroller/.permission.ui.ManagePermissionsActivity (has extras)} from uid 1000
这里不作细述.
对于XXPermissions试了下, 有两点不习惯的地方:
1.要求支持android.support.v4.app.Fragment
ApiTester/src/main/java/com/android/apitester/PermissionTest.java:78: error: cannot access Fragment XXPermissions.with(this) ^ class file for android.support.v4.app.Fragment not found //所以还得增加依赖包 implementation 'com.android.support:appcompat-v7:27.1.1'
2.异常
Caused by: java.lang.IllegalArgumentException: If you have applied for MANAGE_EXTERNAL_STORAGE permissions, do not apply for the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions at com.hjq.permissions.PermissionChecker.optimizeDeprecatedPermission(PermissionChecker.java:239) at com.hjq.permissions.XXPermissions.request(XXPermissions.java:167) at com.android.apitester.PermissionTest.onCreate(PermissionTest.java:34) at android.app.Activity.performCreate(Activity.java:8022) at android.app.Activity.performCreate(Activity.java:8006) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7664) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
参考
1.Android 11 中的存储机制更新
2.分区存储
Android权限适配
android grantRuntimePermission 详解
XXPermissions