请求应用权限的最佳实践

  Google提供的请求应用权限的说明如下:请求应用权限
  官方提供的模板使用了三个条件分支来请求应用权限:
  1.checkSelfPermission用来检查应用是否有需要请求的权限了,如果有,直接执行需要的动作;
  2.shouldShowRequestPermissionRationale在用户曾经点击过拒绝这一权限的选项后为true(非“拒绝不再询问”选项),这时系统发现应用没有响应的权限,开发者可以在这一条件分支加上相关说明的界面,向用户指出申请这个权限的必要性,但是还是有必要在界面向用户提供“拒绝”的选项;
  3.当走到最后一个分支,也就意味着应用还没有响应的权限,且用户不曾点击过拒绝这一权限的选项,于是使用requestPermissions唤起对话框申请权限。

请求应用权限模板

if (ContextCompat.checkSelfPermission(CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==PackageManager.PERMISSION_GRANTED) {// You can use the API that requires the permission.performAction(...);
} else if (shouldShowRequestPermissionRationale(...)) {// In an educational UI, explain to the user why your app requires this// permission for a specific feature to behave as expected. In this UI,// include a "cancel" or "no thanks" button that allows the user to// continue using your app without granting the permission.showInContextUI(...);
} else {// You can directly ask for the permission.requestPermissions(CONTEXT,new String[] { Manifest.permission.REQUESTED_PERMISSION },REQUEST_CODE);
}

检查权限checkSelfPermission

  checkSelfPermission最终会调用到ActivityManager#checkComponentPermission,并且使得传入的pid和uid参数分别为应用的pid和uid。

frameworks/base/core/java/android/app/ContextImpl.java

    @Overridepublic int checkSelfPermission(String permission) {if (permission == null) {throw new IllegalArgumentException("permission is null");}return checkPermission(permission, Process.myPid(), Process.myUid());}@Overridepublic int checkPermission(String permission, int pid, int uid) {if (permission == null) {throw new IllegalArgumentException("permission is null");}final IActivityManager am = ActivityManager.getService();if (am == null) {// Well this is super awkward; we somehow don't have an active// ActivityManager instance. If we're testing a root or system// UID, then they totally have whatever permission this is.final int appId = UserHandle.getAppId(uid);if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);return PackageManager.PERMISSION_GRANTED;}Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " does not hold "+ permission);return PackageManager.PERMISSION_DENIED;}try {return am.checkPermission(permission, pid, uid);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

  AMS端的判断原则是:
  1.对root和system的uid通过检查;
  2.对isolated的uid不通过检查;
  3.对检查的权限是发起检查者定义的情况直接通过;
  4.对访问不开放(android:exported为false)组件情况不通过;
  5.对检查的权限为null的情况直接通过;
  如果上面5条还不能确认结果的话,交给PMS的checkUidPermission函数再进行判断:

frameworks/base/core/java/android/app/ActivityManager.java

    @UnsupportedAppUsagepublic static int checkComponentPermission(String permission, int uid,int owningUid, boolean exported) {// Root, system server get to do everything.final int appId = UserHandle.getAppId(uid);if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {return PackageManager.PERMISSION_GRANTED;}// Isolated processes don't get any permissions.if (UserHandle.isIsolated(uid)) {return PackageManager.PERMISSION_DENIED;}// If there is a uid that owns whatever is being accessed, it has// blanket access to it regardless of the permissions it requires.if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {return PackageManager.PERMISSION_GRANTED;}// If the target is not exported, then nobody else can get to it.if (!exported) {/*RuntimeException here = new RuntimeException("here");here.fillInStackTrace();Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,here);*/return PackageManager.PERMISSION_DENIED;}if (permission == null) {return PackageManager.PERMISSION_GRANTED;}try {return AppGlobals.getPackageManager().checkUidPermission(permission, uid);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

  mCheckPermissionDelegate是一个可嵌入的权限判断实现,由setCheckPermissionDelegateLocked嵌入,如果不为null,则调用嵌入代码的checkUidPermission进行判断。app进行检查的情况下这段嵌入实现为null,所以会调PMS的checkUidPermissionImpl进行判断。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java*

    @Overridepublic int checkUidPermission(String permName, int uid) {final CheckPermissionDelegate checkPermissionDelegate;synchronized (mPackages) {if (mCheckPermissionDelegate == null)  {return checkUidPermissionImpl(permName, uid);}checkPermissionDelegate = mCheckPermissionDelegate;}return checkPermissionDelegate.checkUidPermission(permName, uid,PackageManagerService.this::checkUidPermissionImpl);}

  可以看到,PMS只是过渡一下,将uid转化成对应的包信息PackageParser.Package,再转交给PermissionManagerService。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java*

    private int checkUidPermissionImpl(String permName, int uid) {synchronized (mPackages) {final String[] packageNames = getPackagesForUid(uid);PackageParser.Package pkg = null;final int N = packageNames == null ? 0 : packageNames.length;for (int i = 0; pkg == null && i < N; i++) {pkg = mPackages.get(packageNames[i]);}return mPermissionManager.checkUidPermission(permName, pkg, uid, getCallingUid());}}

  PermissionManagerService的判断原则如下:
  1.申请检查者拥有shareuserid且是instant app的情况不通过;
  2.申请检查者的userid在系统中未启动的情况不通过;
  3.不通过filterAppAccess过滤规则的不通过,主要是关于instant app的;
  4.检查包信息内部的授权情况,授权了的话就通过;(核心)
  5.如果检查的权限是ACCESS_COARSE_LOCATION,只要ACCESS_FINE_LOCATION被授权了就通过;如果检查的权限是INTERACT_ACROSS_USERS,只要INTERACT_ACROSS_USERS_FULL被授权了就通过。

frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java

    private int checkUidPermission(String permName, PackageParser.Package pkg, int uid,int callingUid) {final int callingUserId = UserHandle.getUserId(callingUid);final boolean isCallerInstantApp =mPackageManagerInt.getInstantAppPackageName(callingUid) != null;final boolean isUidInstantApp =mPackageManagerInt.getInstantAppPackageName(uid) != null;final int userId = UserHandle.getUserId(uid);if (!mUserManagerInt.exists(userId)) {return PackageManager.PERMISSION_DENIED;}if (pkg != null) {if (pkg.mSharedUserId != null) {if (isCallerInstantApp) {return PackageManager.PERMISSION_DENIED;}} else if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {return PackageManager.PERMISSION_DENIED;}final PermissionsState permissionsState =((PackageSetting) pkg.mExtras).getPermissionsState();if (permissionsState.hasPermission(permName, userId)) {if (isUidInstantApp) {if (mSettings.isPermissionInstant(permName)) {return PackageManager.PERMISSION_GRANTED;}} else {return PackageManager.PERMISSION_GRANTED;}}if (isImpliedPermissionGranted(permissionsState, permName, userId)) {return PackageManager.PERMISSION_GRANTED;}} else {ArraySet<String> perms = mSystemPermissions.get(uid);if (perms != null) {if (perms.contains(permName)) {return PackageManager.PERMISSION_GRANTED;}if (FULLER_PERMISSION_MAP.containsKey(permName)&& perms.contains(FULLER_PERMISSION_MAP.get(permName))) {return PackageManager.PERMISSION_GRANTED;}}}return PackageManager.PERMISSION_DENIED;}

  对于上述第4点,PackageParser.Package的mExtras是一个PackageSetting对象,PackageSetting里面的mPermissionsState是一个PermissionsState对象,PermissionsState里面的mPermissions是一个ArrayMap,键为String(权限名字符串),值为PermissionData。也就是说一个权限名对应一个PermissionData。PermissionData里面由两个成员变量:1.BasePermission类型的mPerm,BasePermission描述了一个权限的基本信息,例如:name是权限名,type是权限类型,sourcePackageName是定义权限的包名,protectionLevel是权限安全级别,perm包含了PMS获取到的权限信息等等;2.mUserStates描述了每个用户下该BasePermission的授权情况,是一个SparseArray,id是用户id,值是PermissionState(这个PermissionState不同于上面的PermissionState)。安装权限是(installpermission)是针对所有用户的,所以所有安装权限在mUserStates中的id都是-1(UserHandle.USER_ALL);运行时权限是针对不同的有效用户的,所以运行时权限在mUserStates中的id是对应的有效用户(例如0,10等),值部分的PermissionState有一个mGranted用来描述授权状态,一个mFlags用来描述权限的flag。
  类图如下:

shouldShowRequestPermissionRationale展示权限申请原因

  这个函数的调用流程是Activity#shouldShowRequestPermissionRationale->PackageManagerService#shouldShowRequestPermissionRationale。
  先介绍权限的4个flag:FLAG_PERMISSION_SYSTEM_FIXED,FLAG_PERMISSION_POLICY_FIXED,FLAG_PERMISSION_USER_FIXED。
  FLAG_PERMISSION_SYSTEM_FIXED:系统固定,意味着这个权限是系统设定的,应用无法通过grantRuntimePermission/revokeRuntimePermission修改运行时权限的授权状况。这个flag一般由开机授权组件DefaultPermissionGrantPolicy添加,非system的UID不能改动这个flag。
  FLAG_PERMISSION_POLICY_FIXED:设备管理器(DevicePolicyManager)固定,意味着这个权限是DevicePolicyManager设定的,例如全局设置DevicePolicyManager#setPermissionPolicy或者单一应用权限设置 setPermissionGrantState。app除非拥有ADJUST_RUNTIME_PERMISSIONS_POLICY权限,否则无法通过grantRuntimePermission/revokeRuntimePermission修改运行时权限的授权状况,同样,除非拥有ADJUST_RUNTIME_PERMISSIONS_POLICY权限,否则无法改动这个flag。
  FLAG_PERMISSION_USER_FIXED:用户固定,在用户曾经点击过一次拒绝权限的情况下,再点击一次“拒绝,不再询问”的选项后就会为这个权限设置这个flag。这个flag被设置后,运行时权限处于未授权状态,而且不会再弹出相关对话框让用户选择。只有在“应用信息“”的“权限”选项中设置才能重新授权。
  FLAG_PERMISSION_USER_SET:用户设置,在用户首次点击拒绝权限的请款修改被设置。如果上面的FLAG_PERMISSION_XXX_FIXED的flag没有被设置,FLAG_PERMISSION_USER_SET的flag被设置了,shouldShowRequestPermissionRationale就会返回true,这个时候应该向用户展示需要这个权限的原因。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    @Overridepublic boolean shouldShowRequestPermissionRationale(String permissionName,String packageName, int userId) {if (UserHandle.getCallingUserId() != userId) {mContext.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,"canShowRequestPermissionRationale for user " + userId);}final int uid = getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);if (UserHandle.getAppId(getCallingUid()) != UserHandle.getAppId(uid)) {return false;}if (checkPermission(permissionName, packageName, userId)== PackageManager.PERMISSION_GRANTED) {return false;}final int flags;final long identity = Binder.clearCallingIdentity();try {flags = getPermissionFlags(permissionName,packageName, userId);} finally {Binder.restoreCallingIdentity(identity);}final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED| PackageManager.FLAG_PERMISSION_POLICY_FIXED| PackageManager.FLAG_PERMISSION_USER_FIXED;if ((flags & fixedFlags) != 0) {return false;}return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;}

请求权限requestPermissions

  可以看到,requestPermissions主要是构建了一个action是"android.content.pm.action.REQUEST_PERMISSIONS",package是PermissionController的intent,然后唤起相关界面。关于PermissionController apk可以参考谷歌相关说明:
PermissionController

frameworks/base/core/java/android/app/Activity.java

    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {if (requestCode < 0) {throw new IllegalArgumentException("requestCode should be >= 0");}if (mHasCurrentPermissionsRequest) {Log.w(TAG, "Can request only one set of permissions at a time");// Dispatch the callback with empty arrays which means a cancellation.onRequestPermissionsResult(requestCode, new String[0], new int[0]);return;}Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);mHasCurrentPermissionsRequest = true;}

frameworks/base/core/java/android/content/pm/PackageManager.java

    @NonNull@UnsupportedAppUsagepublic Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {if (ArrayUtils.isEmpty(permissions)) {throw new IllegalArgumentException("permission cannot be null or empty");}Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);intent.setPackage(getPermissionControllerPackageName());return intent;}

  以aosp的PermissionController apk为例,响应这个intent的是:
com.android.packageinstaller.permission.ui.GrantPermissionsActivity。
  这个activity内容比较多,onCreate主要的内容是:
  1.授权回调GrantPermissionsViewHandlerImpl;
  2.权限分组AppPermissionGroup;
  3.展示授权交互界面showNextPermissionGroupGrantRequest;

packages/apps/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java

    @Overridepublic void onCreate(Bundle icicle) {...} else {//1.mViewHandler = new com.android.packageinstaller.permission.ui.handheld.GrantPermissionsViewHandlerImpl(this, mCallingPackage, userHandle).setResultListener(this);}...mAppPermissions = new AppPermissions(this, callingPackageInfo, false,new Runnable() {//2.@Overridepublic void run() {setResultAndFinish();}});for (String requestedPermission : mRequestedPermissions) {if (requestedPermission == null) {continue;}ArrayList<String> affectedPermissions =computeAffectedPermissions(requestedPermission);int numAffectedPermissions = affectedPermissions.size();for (int i = 0; i < numAffectedPermissions; i++) {AppPermissionGroup group =mAppPermissions.getGroupForPermission(affectedPermissions.get(i));if (group == null) {reportRequestResult(affectedPermissions.get(i),PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);continue;}addRequestedPermissions(group, affectedPermissions.get(i), icicle == null);}}...if (!showNextPermissionGroupGrantRequest()) {//3setResultAndFinish();}}

权限分组AppPermissionGroup

  先从AppPermissionGroup说起。在Permission Controller的apk实现里面,每个运行时权限都属于一个AppPermissionGroup。

packages/apps/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java

    public static AppPermissionGroup create(Context context, PackageInfo packageInfo,String permissionName, boolean delayChanges) {PermissionInfo permissionInfo;try {permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);} catch (PackageManager.NameNotFoundException e) {return null;}if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)!= PermissionInfo.PROTECTION_DANGEROUS|| (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0|| (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {return null;}String group = Utils.getGroupOfPermission(permissionInfo);PackageItemInfo groupInfo = permissionInfo;if (group != null) {try {groupInfo = context.getPackageManager().getPermissionGroupInfo(group, 0);} catch (PackageManager.NameNotFoundException e) {/* ignore */}}List<PermissionInfo> permissionInfos = null;if (groupInfo instanceof PermissionGroupInfo) {try {permissionInfos = Utils.getPermissionInfosForGroup(context.getPackageManager(),groupInfo.name);} catch (PackageManager.NameNotFoundException e) {/* ignore */}}return create(context, packageInfo, groupInfo, permissionInfos, delayChanges);}

  PermissionController apk内部划分好的权限组有(下面表格按“|权限|权限组|”的形式呈现),如果在表格里面找不到所属的组,则所属权限组由其android:permissionGroup属性决定。需要注意的是:
  1.如果一个app需要若干运行时权限,那么相同组的权限会被放到一个AppPermissionGroup里面,有些前后台权限的情况除外,例如ACCESS_FINE_LOCATION前台权限和ACCESS_COARSE_LOCATION后台权限都属于android.Manifest.permission_group.LOCATION这个权限组,但是它们是放在两个不同的AppPermissionGroup里面的。
  2.同一个权限,由PermissionController apk内部划分好的权限组和定义权限时android:permissionGroup属性指定的权限组可能不是同一个。执行“adb shell pm list permissions -d -g”命令可以看到定义运行时权限时指定的权限组,其中定义ACCESS_FINE_LOCATION权限时指定的是android.permission-group.UNDEFINED,但是在PermissionController apk被划分到android.Manifest.permission_group.LOCATION。

|Manifest.permission.READ_CONTACTS|android.Manifest.permission_group.CONTACTS||Manifest.permission.WRITE_CONTACTS |android.Manifest.permission_group.CONTACTS ||Manifest.permission.GET_ACCOUNTS |android.Manifest.permission_group.CONTACTS ||Manifest.permission.READ_CALENDAR |android.Manifest.permission_group.CALENDAR ||Manifest.permission.WRITE_CALENDAR |android.Manifest.permission_group.CALENDAR ||Manifest.permission.SEND_SMS |android.Manifest.permission_group.SMS ||Manifest.permission.RECEIVE_SMS |android.Manifest.permission_group.SMS ||Manifest.permission.READ_SMS |android.Manifest.permission_group.SMS ||Manifest.permission.RECEIVE_MMS |android.Manifest.permission_group.SMS ||Manifest.permission.RECEIVE_WAP_PUSH |android.Manifest.permission_group.SMS ||Manifest.permission.READ_CELL_BROADCASTS |android.Manifest.permission_group.SMS ||Manifest.permission.READ_EXTERNAL_STORAGE |android.Manifest.permission_group.STORAGE ||Manifest.permission.WRITE_EXTERNAL_STORAGE |android.Manifest.permission_group.STORAGE ||Manifest.permission.ACCESS_MEDIA_LOCATION |android.Manifest.permission_group.STORAGE ||Manifest.permission.ACCESS_FINE_LOCATION |android.Manifest.permission_group.LOCATION ||Manifest.permission.ACCESS_COARSE_LOCATION
|android.Manifest.permission_group.LOCATION ||Manifest.permission.Manifest.permission.ACCESS_BACKGROUND_LOCATION |android.Manifest.permission_group.LOCATION ||Manifest.permission.READ_CALL_LOG|android.Manifest.permission_group.CALL_LOG ||Manifest.permission.WRITE_CALL_LOG|android.Manifest.permission_group.CALL_LOG ||Manifest.permission.PROCESS_OUTGOING_CALLS|android.Manifest.permission_group.CALL_LOG ||Manifest.permission.READ_PHONE_STATE|android.Manifest.permission_group.PHONE ||Manifest.permission.READ_PHONE_NUMBERS|android.Manifest.permission_group.PHONE ||Manifest.permission.CALL_PHONE|android.Manifest.permission_group.PHONE ||Manifest.permission.ADD_VOICEMAIL|android.Manifest.permission_group.PHONE ||Manifest.permission.USE_SIP|android.Manifest.permission_group.PHONE ||Manifest.permission.ANSWER_PHONE_CALLS|android.Manifest.permission_group.PHONE ||Manifest.permission.ACCEPT_HANDOVER|android.Manifest.permission_group.PHONE ||Manifest.permission.RECORD_AUDIO|android.Manifest.permission_group.MICROPHONE ||Manifest.permission.ACTIVITY_RECOGNITION|android.Manifest.permission_group.ACTIVITY_RECOGNITION||Manifest.permission.CAMERA|android.Manifest.permission_group.CAMERA||Manifest.permission.BODY_SENSORS|android.Manifest.permission_group.SENSORS|

  对于应用需要申请的权限集合mRequestedPermissions,对这个集合里面的所有权限按所在的AppPermissionGroup进行分组,形成一个授权分组GroupState。GroupState内部有一个状态值mState,可以是STATE_UNKNOWN(初始默认值),STATE_ALLOWED(允许授权),STATE_DENIED(拒绝授权),STATE_SKIPPED(跳过授权步骤)。GroupState内部还有一个affectedPermissions集合,包含这个授权分组影响到的所有权限。

packages/apps/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java

        for (String requestedPermission : mRequestedPermissions) {if (requestedPermission == null) {continue;}ArrayList<String> affectedPermissions =computeAffectedPermissions(requestedPermission);int numAffectedPermissions = affectedPermissions.size();for (int i = 0; i < numAffectedPermissions; i++) {AppPermissionGroup group =mAppPermissions.getGroupForPermission(affectedPermissions.get(i));if (group == null) {reportRequestResult(affectedPermissions.get(i),PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);continue;}addRequestedPermissions(group, affectedPermissions.get(i), icicle == null);}}

packages/apps/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java

    private void addRequestedPermissions(AppPermissionGroup group, String permName,boolean isFirstInstance) {if (!group.isGrantingAllowed()) {reportRequestResult(permName,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED);// Skip showing groups that we know cannot be granted.return;}Permission permission = group.getPermission(permName);// If the permission is restricted it does not show in the UI and// is not added to the group at all, so check that first.if (permission == null && ArrayUtils.contains(mAppPermissions.getPackageInfo().requestedPermissions, permName)) {reportRequestResult(permName,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION);return;// We allow the user to choose only non-fixed permissions. A permission// is fixed either by device policy or the user denying with prejudice.} else if (group.isUserFixed()) {reportRequestResult(permName,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED);return;} else if (group.isPolicyFixed() && !group.areRuntimePermissionsGranted()|| permission.isPolicyFixed()) {reportRequestResult(permName,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED);return;}Pair<String, Boolean> groupKey = new Pair<>(group.getName(),group.isBackgroundGroup());GroupState state = mRequestGrantPermissionGroups.get(groupKey);if (state == null) {state = new GroupState(group);mRequestGrantPermissionGroups.put(groupKey, state);}state.affectedPermissions = ArrayUtils.appendString(state.affectedPermissions, permName);boolean skipGroup = false;switch (getPermissionPolicy()) {case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: {final String[] filterPermissions = new String[]{permName};group.grantRuntimePermissions(false, filterPermissions);group.setPolicyFixed(filterPermissions);state.mState = GroupState.STATE_ALLOWED;skipGroup = true;reportRequestResult(permName,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED);} break;case DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY: {final String[] filterPermissions = new String[]{permName};group.setPolicyFixed(filterPermissions);state.mState = GroupState.STATE_DENIED;skipGroup = true;reportRequestResult(permName,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED);} break;default: {if (group.areRuntimePermissionsGranted()) {group.grantRuntimePermissions(false, new String[]{permName});state.mState = GroupState.STATE_ALLOWED;skipGroup = true;reportRequestResult(permName,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED);}} break;}if (skipGroup && isFirstInstance) {// Only allow to skip groups when this is the first time the dialog was created.// Otherwise the number of groups changes between instances of the dialog.state.mState = GroupState.STATE_SKIPPED;}}

前后台权限的AppPermissionGroup

  如果一个权限定义时用android:backgroundPermission指定了另一个权限,那么指定者权限被称为前台权限,被指定者权限被称为后台权限。顾名思义,前台权限指的是应用运行在前台可以获得的权限,后台权限指是应用运行在后台可以获得的权限。
  一个AppPermissionGroup包含的权限如果包含了后台权限,那么会将这些后台权限放到另一个AppPermissionGroup中,并记录在第一个AppPermissionGroup的mBackgroundPermissions中。

packages/apps/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java

    public static AppPermissionGroup create(Context context, PackageInfo packageInfo,PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos,CharSequence groupLabel, CharSequence fullGroupLabel, boolean delayChanges) {...// Link up foreground and background permissionsfor (int i = 0; i < allPermissions.size(); i++) {Permission permission = allPermissions.valueAt(i);if (permission.getBackgroundPermissionName() != null) {Permission backgroundPermission = allPermissions.get(permission.getBackgroundPermissionName());if (backgroundPermission != null) {backgroundPermission.addForegroundPermissions(permission);permission.setBackgroundPermission(backgroundPermission);// The background permissions isAppOpAllowed refers to the background state of// the foregound permission's appOp. Hence we can only set it once we know the// matching foreground permission.// @see #allowAppOpif (context.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(permission.getAppOp(), packageInfo.applicationInfo.uid,packageInfo.packageName) == MODE_ALLOWED) {backgroundPermission.setAppOpAllowed(true);}}}}// Add permissions found to this groupfor (int i = 0; i < numPermissions; i++) {Permission permission = allPermissions.valueAt(i);if (permission.isBackgroundPermission()) {if (group.getBackgroundPermissions() == null) {group.mBackgroundPermissions = new AppPermissionGroup(group.mContext,group.getApp(), group.getName(), group.getDeclaringPackage(),group.getLabel(), group.getFullLabel(), group.getDescription(),group.getRequest(), group.getRequestDetail(),group.getBackgroundRequest(), group.getBackgroundRequestDetail(),group.getIconPkg(), group.getIconResId(), group.getUser(),delayChanges, appOpsManager);}group.getBackgroundPermissions().addPermission(permission);} else {if ((!permission.isHardRestricted()|| whitelistedRestrictedPermissions.contains(permission.getName()))&& (!permission.isSoftRestricted()|| SoftRestrictedPermissionPolicy.shouldShow(packageInfo, permission))) {group.addPermission(permission);}}}

授权回调GrantPermissionsViewHandlerImpl

  GrantPermissionsActivity.java内部的onPermissionGrantResult函数根据用户的点击结果调用不同参数的onPermissionGrantResultSingleState。

packages/apps/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java

    @Overridepublic void onPermissionGrantResult(String name,@GrantPermissionsViewHandler.Result int result) {logGrantPermissionActivityButtons(name, result);GroupState foregroundGroupState = getForegroundGroupState(name);GroupState backgroundGroupState = getBackgroundGroupState(name);...switch (result) {case GRANTED_ALWAYS :if (foregroundGroupState != null) {onPermissionGrantResultSingleState(foregroundGroupState, true, false);}if (backgroundGroupState != null) {onPermissionGrantResultSingleState(backgroundGroupState, true, false);}break;case GRANTED_FOREGROUND_ONLY :if (foregroundGroupState != null) {onPermissionGrantResultSingleState(foregroundGroupState, true, false);}if (backgroundGroupState != null) {onPermissionGrantResultSingleState(backgroundGroupState, false, false);}break;case DENIED :if (foregroundGroupState != null) {onPermissionGrantResultSingleState(foregroundGroupState, false, false);}if (backgroundGroupState != null) {onPermissionGrantResultSingleState(backgroundGroupState, false, false);}break;case DENIED_DO_NOT_ASK_AGAIN :if (foregroundGroupState != null) {onPermissionGrantResultSingleState(foregroundGroupState, false, true);}if (backgroundGroupState != null) {onPermissionGrantResultSingleState(backgroundGroupState, false, true);}break;}if (!showNextPermissionGroupGrantRequest()) {setResultAndFinish();}}

  onPermissionGrantResultSingleState由三个参数,第一个groupState是应用权限的所属授权分组,第二个参数是用户选择授权或不授权,第三个参数是用户是否选择了“不再询问”的选项。

packages/apps/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java

    private void onPermissionGrantResultSingleState(GroupState groupState, boolean granted,boolean doNotAskAgain) {if (groupState != null && groupState.mGroup != null&& groupState.mState == GroupState.STATE_UNKNOWN) {if (granted) {groupState.mGroup.grantRuntimePermissions(doNotAskAgain,groupState.affectedPermissions);groupState.mState = GroupState.STATE_ALLOWED;reportRequestResult(groupState.affectedPermissions,PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED);} else {groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,groupState.affectedPermissions);groupState.mState = GroupState.STATE_DENIED;reportRequestResult(groupState.affectedPermissions, doNotAskAgain?PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE: PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED);}}}

  在拒绝权限的revokeRuntimePermissions被调用时,如果时执行了“不再询问”选项的(即是fixedByTheUser为true),会为该权限设置上FLAG_PERMISSION_USER_FIXED的flag,也就照应了之前说的:点击“拒绝,且不再询问”的选项后,shouldShowRequestPermissionRationale会返回true。如果仅仅是执行了“拒绝”的选项(即是fixedByTheUser为false),则会为该权限设置上FLAG_PERMISSION_USER_SET的flag,这样会导致下次展示和用户交互的权限申请对话框时显示不同的内容。在AppPermissionGroup#grantRuntimePermissions和AppPermissionGroup#revokeRuntimePermissions的实现中,在persistChanges函数中会调用PackageManager#grantRuntimePermission和PackageManager#revokeRuntimePermission来实现权限的授予和移除。需要注意的是,一旦AppPermissionGroup#grantRuntimePermissions和AppPermissionGroup#revokeRuntimePermissions执行成功,影响的是这个AppPermissionGroup里面的所有权限,而不是一个。

packages/apps/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java

    public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {...// Do not touch permissions fixed by the system.//不能改变带FLAG_PERMISSION_SYSTEM_FIXED的flag的权限状态,这点授予权限的grantRuntimePermissions也一样if (permission.isSystemFixed()) {wasAllRevoked = false;break;}if (mAppSupportsRuntimePermissions) {// Revoke the permission if needed.if (permission.isGranted()) {permission.setGranted(false);}// Update the permission flags.if (fixedByTheUser) {// Take a note that the user fixed the permission.if (permission.isUserSet() || !permission.isUserFixed()) {permission.setUserSet(false);permission.setUserFixed(true);}} else {if (!permission.isUserSet() || permission.isUserFixed()) {permission.setUserSet(true);permission.setUserFixed(false);}}if (permission.affectsAppOp()) {permission.setAppOpAllowed(false);}} else {// Legacy apps cannot have a non-granted permission but just in case.if (!permission.isGranted()) {continue;}// If the permission has no corresponding app op, then it is a// third-party one and we do not offer toggling of such permissions.if (permission.affectsAppOp()) {if (permission.isAppOpAllowed()) {permission.setAppOpAllowed(false);// Disabling an app op may put the app in a situation in which it// has a handle to state it shouldn't have, so we have to kill the// app. This matches the revoke runtime permission behavior.killApp = true;}// Mark that the permission should not be granted on upgrade// when the app begins supporting runtime permissions.if (!permission.shouldRevokeOnUpgrade()) {permission.setRevokeOnUpgrade(true);}}}}if (!mDelayChanges) {persistChanges(false);if (killApp) {killApp(KILL_REASON_APP_OP_CHANGE);}}return wasAllRevoked;}

展示授权交互界面showNextPermissionGroupGrantRequest

  showNextPermissionGroupGrantRequest负责在将app需要申请的权限分组后,将每个授权分组以一个对话框的形式向用户申请。如图所示:

  授权界面总共由五个按钮,由具体情况决定显示那一部分:“允许”,“总是允许”,“仅在使用此应用允许”,“拒绝”,“拒绝,不要再询问”。
  显示界面的规则如下:
  1.如果当前的GroupState里面有一个权限拥有对应的后台权限(无论app是否声明这个对应的后台权限),则会用“仅在使用此应用允许”按钮取代“允许”按钮;进一步说,如果当前的GroupState带有一个后台AppPermissionGroup或者带有一个拥有后台AppPermissionGroup的前台AppPermissionGroup(参考“前后台权限的AppPermissionGroup”一节),且后台AppPermissionGroup的权限还没被授权,则显示“总是允许”按钮。如果应用申请了ACCESS_COARSE_LOCATION前台权限,但是没有申请ACCESS_BACKGROUND_LOCATION后台权限,这样是不会有“总是允许”按钮的,因为前台AppPermissionGroup没有拥有一个后台AppPermissionGroup。换句话说,“允许”按钮适用于没有前后台区分的运行时权限,“仅在使用此应用允许”是仅仅授予了前台权限,“总是允许”是同时授予了前后台权限。
  2.如果当前的GroupState的权限曾被拒绝了,第二次显示对话框时,则会显示“允许”(非前后台区分台权限被拒绝)/“仅在使用此应用允许”(前台权限被拒绝),“拒绝”,“拒绝,不要再询问”三个按钮。

packages/apps/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java

    private boolean showNextPermissionGroupGrantRequest() {int numGroupStates = mRequestGrantPermissionGroups.size();int numGrantRequests = 0;for (int i = 0; i < numGroupStates; i++) {if (shouldShowRequestForGroupState(mRequestGrantPermissionGroups.valueAt(i))) {numGrantRequests++;}}int currentIndex = 0;for (GroupState groupState : mRequestGrantPermissionGroups.values()) {if (!shouldShowRequestForGroupState(groupState)) {continue;}if (groupState.mState == GroupState.STATE_UNKNOWN) {GroupState foregroundGroupState;GroupState backgroundGroupState;if (groupState.mGroup.isBackgroundGroup()) {backgroundGroupState = groupState;foregroundGroupState = getForegroundGroupState(groupState.mGroup.getName());} else {foregroundGroupState = groupState;backgroundGroupState = getBackgroundGroupState(groupState.mGroup.getName());}CharSequence appLabel = mAppPermissions.getAppLabel();Icon icon;try {icon = Icon.createWithResource(groupState.mGroup.getIconPkg(),groupState.mGroup.getIconResId());} catch (Resources.NotFoundException e) {Log.e(LOG_TAG, "Cannot load icon for group" + groupState.mGroup.getName(), e);icon = null;}// If no background permissions are granted yet, we need to ask for background// permissionsboolean needBackgroundPermission = false;boolean isBackgroundPermissionUserSet = false;if (backgroundGroupState != null) {if (!backgroundGroupState.mGroup.areRuntimePermissionsGranted()) {needBackgroundPermission = true;isBackgroundPermissionUserSet = backgroundGroupState.mGroup.isUserSet();}}// If no foreground permissions are granted yet, we need to ask for foreground// permissionsboolean needForegroundPermission = false;boolean isForegroundPermissionUserSet = false;if (foregroundGroupState != null) {if (!foregroundGroupState.mGroup.areRuntimePermissionsGranted()) {needForegroundPermission = true;isForegroundPermissionUserSet = foregroundGroupState.mGroup.isUserSet();}}// The button doesn't show when its label is nullmButtonLabels = new CharSequence[NUM_BUTTONS];mButtonLabels[LABEL_ALLOW_BUTTON] = getString(R.string.grant_dialog_button_allow);mButtonLabels[LABEL_ALLOW_ALWAYS_BUTTON] = null;mButtonLabels[LABEL_ALLOW_FOREGROUND_BUTTON] = null;mButtonLabels[LABEL_DENY_BUTTON] = getString(R.string.grant_dialog_button_deny);if (isForegroundPermissionUserSet || isBackgroundPermissionUserSet) {mButtonLabels[LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON] =getString(R.string.grant_dialog_button_deny_and_dont_ask_again);} else {mButtonLabels[LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON] = null;}int messageId;int detailMessageId = 0;if (needForegroundPermission) {messageId = groupState.mGroup.getRequest();if (groupState.mGroup.hasPermissionWithBackgroundMode()) {mButtonLabels[LABEL_ALLOW_BUTTON] = null;mButtonLabels[LABEL_ALLOW_FOREGROUND_BUTTON] =getString(R.string.grant_dialog_button_allow_foreground);if (needBackgroundPermission) {mButtonLabels[LABEL_ALLOW_ALWAYS_BUTTON] =getString(R.string.grant_dialog_button_allow_always);if (isForegroundPermissionUserSet || isBackgroundPermissionUserSet) {mButtonLabels[LABEL_DENY_BUTTON] = null;}}} else {detailMessageId = groupState.mGroup.getRequestDetail();}} else {if (needBackgroundPermission) {messageId = groupState.mGroup.getBackgroundRequest();detailMessageId = groupState.mGroup.getBackgroundRequestDetail();mButtonLabels[LABEL_ALLOW_BUTTON] =getString(R.string.grant_dialog_button_allow_background);mButtonLabels[LABEL_DENY_BUTTON] =getString(R.string.grant_dialog_button_deny_background);mButtonLabels[LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON] =getString(R.string.grant_dialog_button_deny_background_and_dont_ask_again);} else {// Not reached as the permissions should be auto-grantedreturn false;}}CharSequence message = getRequestMessage(appLabel, groupState.mGroup, this,messageId);Spanned detailMessage = null;if (detailMessageId != 0) {try {detailMessage = Html.fromHtml(getPackageManager().getResourcesForApplication(groupState.mGroup.getDeclaringPackage()).getString(detailMessageId), 0);} catch (NameNotFoundException ignored) {}}// Set the permission message as the title so it can be announced.setTitle(message);mViewHandler.updateUi(groupState.mGroup.getName(), numGrantRequests, currentIndex,icon, message, detailMessage, mButtonLabels);return true;}if (groupState.mState != GroupState.STATE_SKIPPED) {currentIndex++;}}return false;}

Android权限系统(三):运行时权限检查和申请,PermissionController相关推荐

  1. 聊一聊Android 6.0的运行时权限

    Android 6.0,代号棉花糖,自发布伊始,其主要的特征运行时权限就很受关注.因为这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要做出改变. 没有深入了解运行时权限的开发 ...

  2. Android RuntimePermissions运行时权限:单个运行时权限申请简例

    Android RuntimePermissions运行时权限:单个运行时权限申请简例 Android运行时权限申请的框架结构和步骤比较简单和固定,一般现状代码启动后检查当前的Android SDK版 ...

  3. Android M 新的运行时权限开发者需要知道的一切

    android M 的名字官方刚发布不久,最终正式版即将来临! android在不断发展,最近的更新 M 非常不同,一些主要的变化例如运行时权限将有颠覆性影响.惊讶的是android社区鲜有谈论这事儿 ...

  4. 安卓从业者应该关注:Android 6.0的运行时权限

    Android 6.0,代号棉花糖,自发布伊始,其主要的特征运行时权限就很受关注.因为这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要做出改变. 没有深入了解运行时权限的开发 ...

  5. android方法数据库的权限,Android数据存储,运行时权限

    Android存储目录 Android的底层支持是Linux,不同于Windows,没有分盘的概念,是以文件夹形式呈现,可以理解为一个应用就是一个用户. data目录 手机内部存储目录(手机本身内存) ...

  6. android权限允许,android – 允许多次运行时权限

    我正在编写代码,要求在组中的 android 6.0上获得多个运行时权限.一切都很好,我为此做了一些很好的例子,但仍然有问题. 在ActivityCompat.shouldShowRequestPer ...

  7. (调用系统电话薄)运行时权限的基本使用

    GitHub项目地址: https://github.com/Skymqq/RuntimePermissionTest.git 运行时权限是Android6.0系统引入的新特性,那么为何要引入这种运行 ...

  8. Android 8.0 运行时权限策略变化和适配方案

    Android8.0也就是Android O即将要发布了,有很多新特性,目前我们可以通过AndroidStudio3.0 Canary版本下载Android O最新的系统映像的Developer Pr ...

  9. Android8.0运行时权限策略变化和适配方案

    版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com Android8.0也就是Android O即将要发布了,有很多新特性,目前我们可以通过Android ...

最新文章

  1. Cygwin的安装,卸载,以及安装gdb
  2. cd协议属于计算机网络的应用层,计算机网络 题库 必考 期末 期终 考试 复习 考研 必备...
  3. PaddlePaddle训练营——公开课——AI核心技术掌握——第1章迈入现代人工智能的大门——深度学习的基础算法——反向传播算法简介
  4. f分布表完整图_分布式计算引擎之星——Spark
  5. 七点人脸姿态估计_Github开源库简单配置即可上线的3D人脸检测工具箱
  6. 进一步的去了解正则[一]
  7. 【J2SE】java实现简单照片查看器
  8. oracle 11g的启动和关闭
  9. 2020统计局的行政划分表_湖州市有几个区和县?湖州市2020年县级以上区划名单...
  10. java 单例 初始化_单例数据库对象启动时参数化初始化?
  11. 临时号码,接收短信验证码
  12. VOIP语音电话配置
  13. TBSchedule初识
  14. IBATIS开发指南(夏昕)
  15. MYSQL攻击全攻略
  16. PLC是什么?它的作用是什么?
  17. FairyGUI进阶-滑动列表 虚拟列表 循环列表
  18. 《软件测试的艺术》万字笔记
  19. 广告营销核心干货——《我的营销心得》读书笔记2900字
  20. Spring注解开发以及基于java的容器配置

热门文章

  1. [转一好玩的博文]毕业三年,遇见的傻逼公司大盘点
  2. python中import requests是什么意思_python中requests库使用方法详解
  3. 行人重识别模型搭建与训练
  4. 第一章:costmap_2d代价地图生成原理
  5. 计算机操作系统:处理机调度相关
  6. 磁条卡磁道2的等效数据
  7. Js判断ie浏览器版本
  8. 国际象棋AI(三)---评估
  9. Orcale 批量更新sql
  10. 良心推荐:看完这10部豆瓣高分美剧,英语水平提升几个Level!