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

请求应用权限的最佳实践

  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

    @Override
    public int checkSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        return checkPermission(permission, Process.myPid(), Process.myUid());
    }

    @Override
    public 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

    @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();
        }
    }

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

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

    @Override
    public 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

    @Override
    public 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
    @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;
    }

  以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

    @Override
    public 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.
                    @Override
                    public 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()) {//3
            setResultAndFinish();
        }
    }

权限分组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 permissions
        for (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 #allowAppOp
                    if (context.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
                            permission.getAppOp(), packageInfo.applicationInfo.uid,
                            packageInfo.packageName) == MODE_ALLOWED) {
                        backgroundPermission.setAppOpAllowed(true);
                    }
                }
            }
        }

        // Add permissions found to this group
        for (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

    @Override
    public 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
                // permissions
                boolean 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
                // permissions
                boolean 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 null
                mButtonLabels = 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-granted
                        return 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;
    }

  • 12
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Android中,运行时权限是指在应用程序运行过程中,用户需要对某些危险操作进行授权的权限。在 Android 6.0(即targetSdkVersion < 23)之前,应用程序被授予所有申请权限。然而,在此之后,用户不需要在安装软件时一次性授权所有申请权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。这就是运行时权限的核心。Android将所有权限分为普通权限和危险权限两类。普通权限系统自动帮我们授权的,我们只需在注册文件中声明即可。而危险权限必须经过用户手动点击授权才能使用。危险权限包括9组24个权限。 在程序运行时申请权限的过程中,我们需要首先在AndroidManifest.xml文件中声明所需要的权限。以申请打电话的运行时权限为例,可以在文件中添加如下代码: <uses-permission android:name="android.permission.CALL_PHONE"/> 需要注意的是,在Android 5.1(API 22)或更低版本,并且应用的targetSdkVersion是22或更低版本时,系统会在安装应用时要求用户授权权限。即使在安装时已经授予应用所有权限,在Android 6.0之后依然可以通过"Setting"来关闭已经授予的权限。在请求权限时,系统只告知用户应用需要的权限组,而不告知具体权限。因此,在未检查授权的情况下直接使用危险权限,会导致程序崩溃。为了解决这个问题,我们可以使用v4包中的ContextCompat来处理权限,这样不需要考虑版本问题。相关API包括checkSelfPermission()等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Android运行时权限](https://blog.csdn.net/Cristiano_san/article/details/119840235)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Android 运行时权限终极总结](https://blog.csdn.net/LucasXu01/article/details/80860070)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Invoker123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值