android 权限申请
官方文档:请求运行时权限 | Android 开发者 | Android Developers
官方提供的模板使用了三个条件分支来请求应用权限:
1.checkSelfPermission用来检查应用是否获得 需要请求的权限,如果有权限,直接执行需要的动作;
2.shouldShowRequestPermissionRationale在用户曾经点击过拒绝这一权限的选项后为true(非“拒绝不再询问”选项),这时系统发现应用没有响应的权限,开发者可以在这一条件分支加上相关说明的界面,向用户指出申请这个权限的必要性,但是还是有必要在界面向用户提供“拒绝”的选项;
3.当走到最后一个分支,也就意味着应用还没有响应的权限,且用户不曾点击过拒绝这一权限的选项,于是使用requestPermissions唤起对话框申请权限。
public void requestPermission(String permission){ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { //已经授权 Log.e("TAG","已经授权"); }else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { //拒绝过授权 Log.e("TAG","拒绝过授权"); ActivityCompat.requestPermissions(this, new String[]{permission}, 1); } else { //未授权 Log.e("TAG","未授权"); ActivityCompat.requestPermissions(this, new String[]{permission}, 1); } }
源码追踪
检查权限 checkSelfPermission
checkSelfPermission最终会调用到ActivityManager#checkComponentPermission,并且使得传入的pid和uid参数分别为应用的pid和uid。
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) { ObjectsCompat.requireNonNull(permission, "permission must be non-null"); return context.checkPermission(permission, Process.myPid(), Process.myUid()); } ./frameworks/base/core/java/android/content/Context.java @CheckResult(suggest="#enforcePermission(String,int,int,String)") @PackageManager.PermissionResult public abstract int checkPermission(@NonNull String permission, int pid, int uid);
frameworks\base\core\java\android\app\ContextImpl.java @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { throw new IllegalArgumentException("permission is null"); } if (mParams.isRenouncedPermission(permission) && pid == android.os.Process.myPid() && uid == android.os.Process.myUid()) { Log.v(TAG, "Treating renounced permission " + permission + " as denied"); return PERMISSION_DENIED; } return PermissionManager.checkPermission(permission, pid, uid); }
./frameworks/base/core/java/android/permission/PermissionManager.java /** @hide */ public static int checkPermission(@Nullable String permission, int pid, int uid) { return sPermissionCache.query(new PermissionQuery(permission, pid, uid)); }
private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache = new PropertyInvalidatedCache<PermissionQuery, Integer>( 2048, CACHE_KEY_PACKAGE_INFO, "checkPermission") { @Override protected Integer recompute(PermissionQuery query) { return checkPermissionUncached(query.permission, query.pid, query.uid); } };
/* @hide */ private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) { 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(LOG_TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission); return PackageManager.PERMISSION_GRANTED; } Slog.w(LOG_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(); } }
frameworks\base\core\java\android\app\IActivityManager.aidl @UnsupportedAppUsage int checkPermission(in String permission, int pid, int uid); frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
@Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { return PackageManager.PERMISSION_DENIED; } return checkComponentPermission(permission, pid, uid, -1, true); }
public static int checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported) { if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; } // If there is an explicit permission being checked, and this is coming from a process // that has been denied access to that permission, then just deny. Ultimately this may // not be quite right -- it means that even if the caller would have access for another // reason (such as being the owner of the component it is trying to access), it would still // fail. This also means the system and root uids would be able to deny themselves // access to permissions, which... well okay. ¯\_(ツ)_/¯ if (permission != null) { synchronized (sActiveProcessInfoSelfLocked) { ProcessInfo procInfo = sActiveProcessInfoSelfLocked.get(pid); if (procInfo != null && procInfo.deniedPermissions != null && procInfo.deniedPermissions.contains(permission)) { return PackageManager.PERMISSION_DENIED; } } } return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported); }
frameworks\base\core\java\android\app\ActivityManager.java
/** @hide */ @UnsupportedAppUsage public 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(); } }
AM端的判断原则是:
1.对root和system的uid通过检查;
2.对isolated的uid不通过检查;
3.对检查的权限是发起检查者定义的情况直接通过;
4.对访问不开放(android:exported为false)组件情况不通过;
5.对检查的权限为null的情况直接通过;
如果上面5条还不能确认结果的话,交给PMS的checkUidPermission函数再进行判断:
frameworks\base\core\java\android\app\AppGlobals.java @UnsupportedAppUsage public static IPackageManager getPackageManager() { return ActivityThread.getPackageManager(); } frameworks\base\core\java\android\content\pm\IPackageManager.aidl int checkUidPermission(String permName, int uid); frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java @Override public int checkUidPermission(String permName, int uid) { return mComputer.checkUidPermission(permName, uid); } private final ComputerTracker mComputer = new ComputerTracker(this); ComputerTracker 是PackageManagerService的内部类 /** * This subclass delegates to methods in a Computer after reference-counting the computer. */ private static class ComputerTracker implements Computer {
/** * This class records the Computer being used by a thread and the Computer's reference * count. There is a thread-local copy of this class. */ private static class ThreadComputer { public final int checkUidPermission(String permName, int uid) { ThreadComputer current = snapshot(); try { return current.mComputer.checkUidPermission(permName, uid); } finally { current.release(); } }
// NOTE: Can't remove without a major refactor. Keep around for now. public final int checkUidPermission(String permName, int uid) { return mPermissionManager.checkUidPermission(uid, permName); } PackageManagerService使用 ThreadLocal 和 锁同步了内部的状态 // Internal interface for permission manager private final PermissionManagerServiceInternal mPermissionManager; framework/base/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) int checkUidPermission(int uid, @NonNull String permissionName); frameworks\base\services\core\java\com\android\server\pm\permission\PermissionManagerService.java
private int checkUidPermission(int uid, String permName) { // Not using Objects.requireNonNull() here for compatibility reasons. if (permName == null) { return PackageManager.PERMISSION_DENIED; } final int userId = UserHandle.getUserId(uid); if (!mUserManagerInt.exists(userId)) { return PackageManager.PERMISSION_DENIED; } final CheckPermissionDelegate checkPermissionDelegate; synchronized (mLock) { checkPermissionDelegate = mCheckPermissionDelegate; } if (checkPermissionDelegate == null) { return checkUidPermissionImpl(uid, permName); } return checkPermissionDelegate.checkUidPermission(uid, permName, this::checkUidPermissionImpl); }
mCheckPermissionDelegate是一个可嵌入的权限判断实现,由setCheckPermissionDelegateLocked嵌入,如果不为null,则调用嵌入代码的checkUidPermission进行判断。app进行检查的情况下这段嵌入实现为null,所以会调PMS的checkUidPermissionImpl进行判断。
private int checkUidPermissionImpl(int uid, String permName) { final AndroidPackage pkg = mPackageManagerInt.getPackage(uid); return checkUidPermissionInternal(pkg, uid, permName); }
private int checkUidPermissionInternal(@Nullable AndroidPackage pkg, int uid, @NonNull String permissionName) { if (pkg != null) { final int userId = UserHandle.getUserId(uid); return checkPermissionInternal(pkg, false, permissionName, userId); } synchronized (mLock) { if (checkSingleUidPermissionInternalLocked(uid, permissionName)) { return PackageManager.PERMISSION_GRANTED; } final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); if (fullerPermissionName != null && checkSingleUidPermissionInternalLocked(uid, fullerPermissionName)) { return PackageManager.PERMISSION_GRANTED; } } return PackageManager.PERMISSION_DENIED; }
private int checkPermissionInternal(@NonNull AndroidPackage pkg, boolean isPackageExplicit, @NonNull String permissionName, @UserIdInt int userId) { final int callingUid = getCallingUid(); if (isPackageExplicit || pkg.getSharedUserId() == null) { if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { return PackageManager.PERMISSION_DENIED; } } else { if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { return PackageManager.PERMISSION_DENIED; } } final int uid = UserHandle.getUid(userId, pkg.getUid()); final boolean isInstantApp = mPackageManagerInt.getInstantAppPackageName(uid) != null; synchronized (mLock) { final UidPermissionState uidState = getUidStateLocked(pkg, userId); if (uidState == null) { Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + userId); return PackageManager.PERMISSION_DENIED; } if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) { return PackageManager.PERMISSION_GRANTED; } final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); if (fullerPermissionName != null && checkSinglePermissionInternalLocked(uidState, fullerPermissionName, isInstantApp)) { return PackageManager.PERMISSION_GRANTED; } } return PackageManager.PERMISSION_DENIED; }
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被授权了就通过。
shouldShowRequestPermissionRationale展示权限申请原因
这个函数的调用流程是Activity#shouldShowRequestPermissionRationale->PackageManager#shouldShowRequestPermissionRationale-》ApplicationPackageManager#shouldShowRequestPermissionRationale-》。
先介绍权限的4个flag:FLAG_PERMISSION_SYSTEM_FIXED,FLAG_PERMISSION_POLICY_FIXED,FLAG_PERMISSION_USER_FIXED,FLAG_PERMISSION_USER_SET。
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,这个时候应该向用户展示需要这个权限的原因。
ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission) { if (Build.VERSION.SDK_INT >= 23) { return Api23Impl.shouldShowRequestPermissionRationale(activity, permission); } return false; }
@DoNotInline static boolean shouldShowRequestPermissionRationale(Activity activity, String permission) { return activity.shouldShowRequestPermissionRationale(permission); } public boolean shouldShowRequestPermissionRationale(@NonNull String permission) { return getPackageManager().shouldShowRequestPermissionRationale(permission); } frameworks\base\core\java\android\content\pm\PackageManager.java @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName); frameworks\base\core\java\android\app\ApplicationPackageManager.java
public class ApplicationPackageManager extends PackageManager @Override @UnsupportedAppUsage public boolean shouldShowRequestPermissionRationale(String permName) { return getPermissionManager().shouldShowRequestPermissionRationale(permName); }
frameworks\base\core\java\android\permission\PermissionManager.java
//@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public boolean shouldShowRequestPermissionRationale(@NonNull String permissionName) { try { final String packageName = mContext.getPackageName(); return mPermissionManager.shouldShowRequestPermissionRationale(packageName, permissionName, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
frameworks\base\services\core\java\com\android\server\pm\permission\PermissionManagerService.java
@Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, @UserIdInt int userId) { final int callingUid = Binder.getCallingUid(); if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, "canShowRequestPermissionRationale for user " + userId); } final int uid = mPackageManagerInt.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId); if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(uid)) { return false; } if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) { return false; } final int flags; final long identity = Binder.clearCallingIdentity(); try { flags = getPermissionFlagsInternal(packageName, permName, callingUid, 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; } synchronized (mLock) { final Permission permission = mRegistry.getPermission(permName); if (permission == null) { return false; } if (permission.isHardRestricted() && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { return false; } } final long token = Binder.clearCallingIdentity(); try { if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId)) { return true; } } catch (RemoteException e) { Log.e(TAG, "Unable to check if compatibility change is enabled.", e); } finally { Binder.restoreCallingIdentity(token); } return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; }
请求权限requestPermissions
可以看到,requestPermissions主要是构建了一个action是"android.content.pm.action.REQUEST_PERMISSIONS",package是PermissionController的intent,然后唤起相关界面。关于PermissionController apk可以参考谷歌相关说明:
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; } if (!getAttributionSource().getRenouncedPermissions().isEmpty()) { final int permissionCount = permissions.length; for (int i = 0; i < permissionCount; i++) { if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) { throw new IllegalArgumentException("Cannot request renounced permission: " + permissions[i]); } } } final 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 @UnsupportedAppUsage public 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; }
packages\modules\Permission\PermissionController响应这个intent的是:packages\modules\Permission\PermissionController\src\com\android\permissioncontroller\permission\ui\GrantPermissionsActivity.java
这个activity内容比较多,onCreate主要的内容是:
1.授权回调GrantPermissionsViewHandlerImpl;
2.权限分组AppPermissionGroup;
3.展示授权交互界面showNextPermissionGroupGrantRequest;
权限分组AppPermissionGroup
先从AppPermissionGroup说起。在Permission Controller的apk实现里面,每个运行时权限都属于一个AppPermissionGroup。
packages\modules\Permission\PermissionController\src\com\android\permissioncontroller\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。
3.对于应用需要申请的权限集合mRequestedPermissions,对这个集合里面的所有权限按所在的AppPermissionGroup进行分组,形成一个授权分组GroupState。GroupState内部有一个状态值mState,可以是STATE_UNKNOWN(初始默认值),STATE_ALLOWED(允许授权),STATE_DENIED(拒绝授权),STATE_SKIPPED(跳过授权步骤)。GroupState内部还有一个affectedPermissions集合,包含这个授权分组影响到的所有权限。
前后台权限的AppPermissionGroup
如果一个权限定义时用android:backgroundPermission指定了另一个权限,那么指定者权限被称为前台权限,被指定者权限被称为后台权限。顾名思义,前台权限指的是应用运行在前台可以获得的权限,后台权限指是应用运行在后台可以获得的权限。
一个AppPermissionGroup包含的权限如果包含了后台权限,那么会将这些后台权限放到另一个AppPermissionGroup中,并记录在第一个AppPermissionGroup的mBackgroundPermissions中。