【Android】通过grantRuntimePermission、revokeRuntimePermission获取和回收运行时权限

需求:不需要用户点击允许按钮直接获取READ_PHONE_STATE等权限
分析:因为用户可以在手机中通过打开设置-应用-选择具体应用-权限界面,手动打开用户所需要的权限(界面效果如图),我就想我怎么样可以在代码中模拟点击按钮打开权限的操作,于是我就去查看源码。
第一步:通过adb shell dumpsys activity | grep "mFocusedActivity"命令,我们可以发现当前页面是com.android.packageinstaller/.permission.ui.ManagePermissionsActivity,于是我就去google下载了packagerinstaller的apk,找到了ManagePermissionActivity的源码,然后发现activity中添加的是AppPermissionsFragment
//AppPermissionsFragment源码 @Override public boolean onPreferenceChange(final Preference preference, Object newValue) { String groupName = preference.getKey(); final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); if (group == null) { return false; } addToggledGroup(group); if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) { LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel()); return false; } if (newValue == Boolean.TRUE) { group.grantRuntimePermissions(false); } else { final boolean grantedByDefault = group.hasGrantedByDefaultPermission(); if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) { new AlertDialog.Builder(getContext()) .setMessage(grantedByDefault ? R.string.system_warning : R.string.old_sdk_deny_warning) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.grant_dialog_button_deny_anyway, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ((SwitchPreference) preference).setChecked(false); group.revokeRuntimePermissions(false); if (!grantedByDefault) { mHasConfirmedRevoke = true; } } }) .show(); return false; } else { group.revokeRuntimePermissions(false); } } return true; }
第二步:通过查看AppPermissionsFragment源码,我们发现调用的是group.grantRuntimePermissions(false)和 group.revokeRuntimePermissions(false),group是AppPermissionGroup,发现在AppPermissionGroup最后调用的就是PackageManager的grantRuntimePermission((以grantRuntimePermissions分析))。
//AppPermissionGroup源码 public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { final int uid = mPackageInfo.applicationInfo.uid; // We toggle permissions only to apps that support runtime // permissions, otherwise we toggle the app op corresponding // to the permission if the permission is granted to the app. for (Permission permission : mPermissions.values()) { if (filterPermissions != null && !ArrayUtils.contains(filterPermissions, permission.getName())) { continue; } if (mAppSupportsRuntimePermissions) { // Do not touch permissions fixed by the system. if (permission.isSystemFixed()) { return false; } // Ensure the permission app op enabled before the permission grant. if (permission.hasAppOp() && !permission.isAppOpAllowed()) { permission.setAppOpAllowed(true); mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED); } // Grant the permission if needed. if (!permission.isGranted()) { permission.setGranted(true); mPackageManager.grantRuntimePermission(mPackageInfo.packageName, permission.getName(), mUserHandle); } // Update the permission flags. if (!fixedByTheUser) { // Now the apps can ask for the permission as the user // no longer has it fixed in a denied state. if (permission.isUserFixed() || permission.isUserSet()) { permission.setUserFixed(false); permission.setUserSet(false); mPackageManager.updatePermissionFlags(permission.getName(), mPackageInfo.packageName, PackageManager.FLAG_PERMISSION_USER_FIXED | PackageManager.FLAG_PERMISSION_USER_SET, 0, mUserHandle); } } } else { // Legacy apps cannot have a not granted permission but just in case. if (!permission.isGranted()) { continue; } int killUid = -1; int mask = 0; // If the permissions has no corresponding app op, then it is a // third-party one and we do not offer toggling of such permissions. if (permission.hasAppOp()) { if (!permission.isAppOpAllowed()) { permission.setAppOpAllowed(true); // Enable the app op. mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED); // Legacy apps do not know that they have to retry access to a // resource due to changes in runtime permissions (app ops in this // case). Therefore, we restart them on app op change, so they // can pick up the change. killUid = uid; } // Mark that the permission should not be be granted on upgrade // when the app begins supporting runtime permissions. if (permission.shouldRevokeOnUpgrade()) { permission.setRevokeOnUpgrade(false); mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; } } if (mask != 0) { mPackageManager.updatePermissionFlags(permission.getName(), mPackageInfo.packageName, mask, 0, mUserHandle); } if (killUid != -1) { mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); } } } return true; }
第三步:android 7上查看PackageManager源码
@SystemApi public abstract void grantRuntimePermission(@NonNull String packageName, @NonNull String permissionName, @NonNull UserHandle user); @SystemApi public abstract void revokeRuntimePermission(@NonNull String packageName, @NonNull String permissionName, @NonNull UserHandle user);
第四步:通过源码我们可以看到PM中的grant和revoke方法都是系统方法。调用grant和revoke需要系统权限,调用方法如下:
if (para != null) { try { String pkg = para.getString(KEY_PKG_NAME); String permissionName = para.getString(PERMISSION_NAME); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { PackageManager packageManager = mContext.getPackageManager(); Method method = packageManager.getClass().getDeclaredMethod("grantRuntimePermission", String.class, String.class, UserHandle.class); method.setAccessible(true); method.invoke(packageManager, pkg, permissionName, Process.myUserHandle()); } else { LogUtils.e(TAG, "android os version is too low"); } } catch (Exception e) { LogUtils.e(TAG, "error when SERVICE_EXTENDED_API_SET_NOTICICATION_OBSERVIER"); e.printStackTrace(); } }
注意:在grantRuntimePermission的时候,permissionName传值如果是Manifest.permission.READ_PHONE_STATE是可以的,但是revokeRuntimePermission的时候permissionName必须是android.permission.READ_PHONE_STATE,当执行revokeRuntimePermission的时候,被回收权限的应用会直接退出,如log所示03-30 06:57:46.585 1418-2509/system_process I/ActivityManager: Killing 4179:com.xxxxxxx.xxxxxxxx/u0a102 (adj 0): permissions revoked
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库