Android M权限管理

时间:2021-11-21 15:18:38

Android的权限管理越来越完善,但是牵涉的内容也是更多了:从4.4的AppOps到6.0的Runtime Permission,Google还是为之做了不少努力。

AppOps简介:

Android 4.4加入的权限管理:用户在安装应用时,会弹窗列举申请的权限,用户授权才能正常安装,因此只要安装了的应用就会获取所有权限。部分三方OEM厂商会将安装授予的权限改为询问,提高安全性。因为是安装授予的权限,我们暂且把AppOps管理的权限称为“安装权限”
虽然AppOps在4.4没有对外开放,但仍是后续版本权限管理的基础。

Runtime Permission简介:

Android 6.0启用的权限管理:在应用运行时,需要动态的申请权限,而不仅仅是在Manifest中申明就行了。依据其特性,我们称之为“运行时权限”。6.0中对于sdk >= 23的应用,安装权限都是默认赋予的。

运行时权限与安装权限是相辅相成的,只有同时具备两种权限,应用才能正常使用对应功能。需要指出的是:运行时权限仅针对api >= 23的应用,对于api < 23的应用,仍沿用AppOps的规则。

权限的检查:

在Activity中检查应用是否有权限,ContextWrapper.java:

@Override
public int checkSelfPermission(String permission) {
   return mBase.checkSelfPermission(permission);
}

其具体实现在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");
        }

        try {
            return ActivityManagerNative.getDefault().checkPermission(
                    permission, pid, uid);
        } catch (RemoteException e) {
            return PackageManager.PERMISSION_DENIED;
        }
    }

走入了ActivityManagerNative.java中,ActivityManagerNative.getDefault()获取到的是一个IActivityManager对象,这实际上是ActivityManagerService的一个远程代理,具体实现在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);
    }

int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }

如果是MY_PID,直接赋予权限,否则调用ActivityManager.java的checkComponentPermission方法:

 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) {
            // Should never happen, but if it does... deny!
            Slog.e(TAG, "PackageManager is dead?!?", e);
        }
        return PackageManager.PERMISSION_DENIED;
    }

ROOT_UID和SYSTEM_UID也是直接赋予权限的。在上面代码中owningUid是设置为-1的,因此最终会走到AppGlobals.getPackageManager()
.checkUidPermission(permission, uid),通过PackageManager来查询权限,其具体实现在PackageManagerService.java

public int checkUidPermission(String permName, int uid) {
        final int userId = UserHandle.getUserId(uid);

        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
        //依据uid查询,获取一个应用的状态对象,判断该应用是否具有该权限
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                final SettingBase ps = (SettingBase) obj;
                final PermissionsState permissionsState = ps.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } 
            //此处处理系统应用的状态,延伸下去发现是从xml文件读取的默认权限。
            else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
            }
        }

        return PackageManager.PERMISSION_DENIED;
    }

至此运行时权限的查询流程走完了,我们发现运行时权限最终是落脚于PackageManagerService中的,这里才是运行时权限的核心。至于运行时权限的设置、持久化是如何进行,在下文权限的申请中讨论,当然也是在PackageManagerService中。

权限的申请:

动态权限的申请用requestPermissions来进行,从Activity作为起点:

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (mHasCurrentPermissionsRequest) {
            Log.w(TAG, "Can reqeust 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;
    }

Intent的获取是通过PackageManager来获取的,用的是startActivityForResult方法来启动权限申请对话框,便于返回结果:

public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
        if (ArrayUtils.isEmpty(permissions)) {
           throw new NullPointerException("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;
    }

intent的Action为ACTION_REQUEST_PERMISSIONS,隐式的调用GrantPermissionsActivity来进行授权。UI部分在GrantPermissionsViewHandler中处理,具体授权仍在GrantPermissionActivity中,具体授权代码如下:

@Override
    public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
        if (isObscuredTouch()) {
            showOverlayDialog();
            finish();
            return;
        }
        GroupState groupState = mRequestGrantPermissionGroups.get(name);
        if (groupState.mGroup != null) {
            if (granted) {
                groupState.mGroup.grantRuntimePermissions(doNotAskAgain);
                groupState.mState = GroupState.STATE_ALLOWED;
            } else {
                groupState.mGroup.revokeRuntimePermissions(doNotAskAgain);
                groupState.mState = GroupState.STATE_DENIED;
            }
            updateGrantResults(groupState.mGroup);
        }
        if (!showNextPermissionGroupGrantRequest()) {
            setResultAndFinish();
        }
    }

我们看到grantRuntimePermissions是AppPermissionGroup的方法,这是PackageInstaller中的一个类,在6.0中运行时权限也都是PackageInstaller这个系统应用在管理。从名字我们也可以看出,这个应用也负责应用的安装、卸载。

public boolean grantRuntimePermissions(boolean fixedByTheUser) {
        final boolean isSharedUser = mPackageInfo.sharedUserId != null;
        final int uid = mPackageInfo.applicationInfo.uid;

        // We toggle permissions only to apps that support runtime
        // permissions, otherwise we toggle the app op corresponding
        // to the permission if the permission is granted to the app.
        for (Permission permission : mPermissions.values()) {
            if (mAppSupportsRuntimePermissions) {
                // Do not touch permissions fixed by the system.
                if (permission.isSystemFixed()) {
                    return false;
                }

                // Ensure the permission app op enabled before the permission grant.
                if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                    mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                }

                // Grant the permission if needed.
                if (!permission.isGranted()) {
                    permission.setGranted(true);
                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                }

                // Update the permission flags.
                if (!fixedByTheUser) {
                    // Now the apps can ask for the permission as the user
                    // no longer has it fixed in a denied state.
                    if (permission.isUserFixed() || permission.isUserSet()) {
                        permission.setUserFixed(false);
                        permission.setUserSet(true);
                        mPackageManager.updatePermissionFlags(permission.getName(),
                                mPackageInfo.packageName,
                                PackageManager.FLAG_PERMISSION_USER_FIXED
                                        | PackageManager.FLAG_PERMISSION_USER_SET,
                                0, mUserHandle);
                    }
                }
            } else {
                // Legacy apps cannot have a not granted permission but just in case.
                // Also if the permissions has no corresponding app op, then it is a
                // third-party one and we do not offer toggling of such permissions.
                if (!permission.isGranted() || !permission.hasAppOp()) {
                    continue;
                }

                if (!permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                    // It this is a shared user we want to enable the app op for all
                    // packages in the shared user to match the behavior of this
                    // shared user having a runtime permission.
                    if (isSharedUser) {
                        // Enable the app op.
                        String[] packageNames = mPackageManager.getPackagesForUid(uid);
                        for (String packageName : packageNames) {
                            mAppOps.setUidMode(permission.getAppOp(), uid,
                                    AppOpsManager.MODE_ALLOWED);
                        }
                    } else {
                        // Enable the app op.
                        mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                    }

                    // Mark that the permission should not be be granted on upgrade
                    // when the app begins supporting runtime permissions.
                    if (permission.shouldRevokeOnUpgrade()) {
                        permission.setRevokeOnUpgrade(false);
                        mPackageManager.updatePermissionFlags(permission.getName(),
                                mPackageInfo.packageName,
                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
                                0, mUserHandle);
                    }

                    // Legacy apps do not know that they have to retry access to a
                    // resource due to changes in runtime permissions (app ops in this
                    // case). Therefore, we restart them on app op change, so they
                    // can pick up the change.
                    mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
                }
            }
        }

        return true;
    }

permissionsState.grantRuntimePermission
与checkUidPermission中的permissionsState.hasPermission相呼应。
mSettings.writeRuntimePermissionsForUserLPr(userId, false)进行持久化,将结果写入runtime-permissions.xml。

未完待续。。。

参考:
http://blog.csdn.net/lewif/article/details/49124757
http://mobile.51cto.com/android-526905.htm