android M 之前应用权限和M 之后的应用权限控制

转载标明出处:
https://blog.csdn.net/shift_wwx/article/details/80674768

相关资源:

Android Runtime Permission 详解

android grantRuntimePermission 详解

android GrantPermissionsActivity 详解

AppOps 对于Normal permission 的控制

Android native 权限控制流程

AppOps 中setUidMode 和setMode区别


前言:

最近搞权限控制的流程,从Android Runtime Permission 详解 中了解到android 系统在M 之后对于权限的控制是不一样的流程。那对于M 之前的apk 当然没有M 之后系统中的接口,例如checkPermission。在M 之后的应用中都会通过checkPermission 的接口来确认权限是否是granted,M 之前的应用无法使用checkPermission,系统为此将这一部分的流程控制加到了AppOps 中。本文主要通过源码来分析M 系统中对于M 之前app 权限控制预留的流程。


源码解析:

对于M 之后的权限控制可以看Android Runtime Permission 详解

首先来看startActivity:
    @Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
                resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }
接着会调用startActivityAsUser,后面的流程源码不贴出来了(占用空间)。


最后会调用到ActivityStarter 中的startActivity:
    /** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */
    private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, TaskRecord inTask) {

        ...
        ...

        boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
                requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
                resultRecord, resultStack, options);
        abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                callingPid, resolvedType, aInfo.applicationInfo);
				
        ...
        ...
    }

在这里会对启动的任何activity进行权限的访问,确认是否要abort,那一般对于M 之后的应用一般都是allowed,这个后面会解释。接着来看checkStartAnyActivityPermission:

boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
            String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
            ActivityRecord resultRecord, ActivityStack resultStack, ActivityOptions options) {
        final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
                callingUid); //check step 1
        if (startAnyPerm == PERMISSION_GRANTED) {
            return true;
        }
        final int componentRestriction = getComponentRestrictionForCallingPackage(
                aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity); //check step 2
        final int actionRestriction = getActionRestrictionForCallingPackage(
                intent.getAction(), callingPackage, callingPid, callingUid); //check step 3
        if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
                || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(-1,
                        resultRecord, resultWho, requestCode,
                        Activity.RESULT_CANCELED, null);
            }
            final String msg;
            if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
                msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")" + " with revoked permission "
                        + ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
            } else if (!aInfo.exported) {
                msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")"
                        + " not exported from uid " + aInfo.applicationInfo.uid;
            } else {
                msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")"
                        + " requires " + aInfo.permission;
            }
            Slog.w(TAG, msg);
            throw new SecurityException(msg); //throw an exception for denied state
        }

        if (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {
            final String message = "Appop Denial: starting " + intent.toString()
                    + " from " + callerApp + " (pid=" + callingPid
                    + ", uid=" + callingUid + ")"
                    + " requires " + AppOpsManager.permissionToOp(
                            ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));
            Slog.w(TAG, message);
            return false;
        } else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {
            final String message = "Appop Denial: starting " + intent.toString()
                    + " from " + callerApp + " (pid=" + callingPid
                    + ", uid=" + callingUid + ")"
                    + " requires appop " + AppOpsManager.permissionToOp(aInfo.permission);
            Slog.w(TAG, message);
            return false;
        }
        if (options != null) {
            if (options.getLaunchTaskId() != INVALID_STACK_ID) {
                final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
                        callingPid, callingUid);
                if (startInTaskPerm == PERMISSION_DENIED) {
                    final String msg = "Permission Denial: starting " + intent.toString()
                            + " from " + callerApp + " (pid=" + callingPid
                            + ", uid=" + callingUid + ") with launchTaskId="
                            + options.getLaunchTaskId();
                    Slog.w(TAG, msg);
                    throw new SecurityException(msg);
                }
            }
            // Check if someone tries to launch an activity on a private display with a different
            // owner.
            final int launchDisplayId = options.getLaunchDisplayId();
            if (launchDisplayId != INVALID_DISPLAY && !isCallerAllowedToLaunchOnDisplay(callingPid,
                    callingUid, launchDisplayId, aInfo)) {
                final String msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ") with launchDisplayId="
                        + launchDisplayId;
                Slog.w(TAG, msg);
                throw new SecurityException(msg);
            }
        }

        return true;
    }

这段code 比较简单,通过三个check 函数,确定状态是否正常,不正常直接会给出securityException,这样就要求check 的返回状态必须正常。

1、mService.checkPermission(START_ANY_ACTIVITY, callingPid, callingUid);

注意这里的权限是START_ANY_ACTIVITY:

    <!-- Allows an application to start any activity, regardless of permission
         protection or exported state.
         @hide -->
    <permission android:name="android.permission.START_ANY_ACTIVITY"
        android:protectionLevel="signature" />

注册这个权限的应用可以启动任何的activity,如果有这个权限,那么其他的权限管理就形同虚设了。

2、getComponentRestrictionForCallingPackage()
    private int getComponentRestrictionForCallingPackage(ActivityInfo activityInfo,
            String callingPackage, int callingPid, int callingUid, boolean ignoreTargetSecurity) {
        if (!ignoreTargetSecurity && mService.checkComponentPermission(activityInfo.permission,
                callingPid, callingUid, activityInfo.applicationInfo.uid, activityInfo.exported)
                == PERMISSION_DENIED) {
            return ACTIVITY_RESTRICTION_PERMISSION;
        }

        if (activityInfo.permission == null) {
            return ACTIVITY_RESTRICTION_NONE;
        }

        final int opCode = AppOpsManager.permissionToOpCode(activityInfo.permission);
        if (opCode == AppOpsManager.OP_NONE) {
            return ACTIVITY_RESTRICTION_NONE;
        }

        if (mService.mAppOpsService.noteOperation(opCode, callingUid,
                callingPackage) != AppOpsManager.MODE_ALLOWED) {
            if (!ignoreTargetSecurity) {
                return ACTIVITY_RESTRICTION_APPOP;
            }
        }

        return ACTIVITY_RESTRICTION_NONE;
    }

最开始是通过M 之后的checkPermission 接口检测,如果这里都过不了,那肯定无需进AppOps 了。

这里可以看到对于给出的权限,例如PHONE_CALLS,都会经过noteOperation 的操作,不管是否是M 之前还是之后,唯一的区别可能就是在里面的逻辑控制了。如果这里不过的话,会给出ACTIVITY_RESTRICTION_APPOP 的返回,以后的处理逻辑这里不多说了,代码很清晰。

3、getActionRestrictionForCallingPackage()
    private int getActionRestrictionForCallingPackage(String action,
            String callingPackage, int callingPid, int callingUid) {
        if (action == null) {
            return ACTIVITY_RESTRICTION_NONE;
        }

        String permission = ACTION_TO_RUNTIME_PERMISSION.get(action);
        if (permission == null) {
            return ACTIVITY_RESTRICTION_NONE;
        }

        final PackageInfo packageInfo;
        try {
            packageInfo = mService.mContext.getPackageManager()
                    .getPackageInfo(callingPackage, PackageManager.GET_PERMISSIONS);
        } catch (PackageManager.NameNotFoundException e) {
            Slog.i(TAG, "Cannot find package info for " + callingPackage);
            return ACTIVITY_RESTRICTION_NONE;
        }

        if (!ArrayUtils.contains(packageInfo.requestedPermissions, permission)) {
            return ACTIVITY_RESTRICTION_NONE;
        }

        if (mService.checkPermission(permission, callingPid, callingUid) == PERMISSION_DENIED) {
            return ACTIVITY_RESTRICTION_PERMISSION;
        }

        final int opCode = AppOpsManager.permissionToOpCode(permission);
        if (opCode == AppOpsManager.OP_NONE) {
            return ACTIVITY_RESTRICTION_NONE;
        }

        if (mService.mAppOpsService.noteOperation(opCode, callingUid,
                callingPackage) != AppOpsManager.MODE_ALLOWED) {
            return ACTIVITY_RESTRICTION_APPOP;
        }

        return ACTIVITY_RESTRICTION_NONE;
    }

这里是通过action 来确认permission,然后通过permission 继续进行M 之后的接口checkPermission 和AppOps 的验证。


总结:

这里只是列举了android M 之后的系统在startActivity 的时候对于权限管理的控制流程,对于M 之前的应用,一般checkPemission 都是给的granted,至于这个默认值如果想知道的可以看PMS 或者给我留言。那如果对于M 之前的应用,想要权限控制,而且不改变原来系统控制流程,只能在AppOps 中控制。

当然,除了这里的startActivity有权限的控制,还有其他的,例如ContentProvider。

        @Override
        public int update(String callingPkg, Uri uri, ContentValues values, String selection,
                String[] selectionArgs) {
            validateIncomingUri(uri);
            uri = maybeGetUriWithoutUserId(uri);
            if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                return 0;
            }
            final String original = setCallingPackage(callingPkg);
            try {
                return ContentProvider.this.update(uri, values, selection, selectionArgs);
            } finally {
                setCallingPackage(original);
            }
        }


对话框提示

通过android GrantPermissionsActivity 详解,M 之后的应用中都会有对话框弹出,那M 之前的应用弹框提示只能在AppOps中控制(之前在AMS 中的checkPermission 中控制,但是发现会一堆死锁,修改的code 最后贴出)。

由于一些原因,这里只贴一部分code:

if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
    final int uidMode = uidState.opModes.get(switchCode);
    if (uidMode != AppOpsManager.MODE_ALLOWED) {
        if (DEBUG) Log.d(TAG, "noteOperation: (uidState)reject #" + op.mode + " for code "
	    + switchCode + " (" + code + ") uid " + uid + " package "
	    + packageName);
        op.rejectTime = System.currentTimeMillis();
        final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
        req = enforcePermission(code, uid, packageName, switchOp);
        if (req == null)
	    return uidMode;
        isAllowed = false;
    }
} 
    private PermissionDialogReq enforcePermission(int code, int uid, String packageName, Op op) {
        if (!isStrictOpEnable()
            || !isStrictCodeValid(code)
            || Looper.myLooper() == mLooper
            || op.mode != AppOpsManager.MODE_NOTED)
            return null;

        return askOperationLocked(code, uid, packageName, op);
    }

    private boolean isStrictCodeValid(int code) {
        return code == AppOpsManager.OP_CHANGE_WIFI_STATE
            || code == AppOpsManager.OP_BLUETOOTH_ADMIN
            || code == AppOpsManager.OP_NFC
            || code == AppOpsManager.OP_SEND_MMS
            || isRuntimePermission(code);
    }

    /**
     * added by wj, just adjust the permission is runtime permission or not
     * for the application whose SDK version is less than M.
     */
    private boolean isRuntimePermission(int code) {
        String permName = AppOpsManager.opToPermission(code);

        PermissionInfo permissionInfo;
        try {
            permissionInfo = mContext.getPackageManager().getPermissionInfo(permName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }

        return (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
            == PermissionInfo.PROTECTION_DANGEROUS;
    }

在这基础上搞个自定义的dialog,完成实现。

这里的MODE 在原来ALLOWED 和 IGNORED 之外,多加了一个NOTED,表示每次都提示。


附加:

1、应用中对于MODE_NOTED 模式处理:

   public boolean noteRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
        final int uid = mPackageInfo.applicationInfo.uid;

        // We toggle permissions only to apps that support runtime
        // permissions, otherwise we toggle the app op corresponding
        // to the permission if the permission is granted to the app.
        for (Permission permission : mPermissions.values()) {
            if (filterPermissions != null && !ArrayUtils.contains(filterPermissions, permission.getName())) {
                continue;
            }

            if (mAppSupportsRuntimePermissions && mIsRuntimePermission && !mIsSendMMS) {
                // Do not touch permissions fixed by the system.
                if (permission.isSystemFixed()) {
                    return false;
                }

                if (permission.hasAppOp() && !permission.isAppOpNoted()) {
                    permission.setAppOpMode(AppOpsManager.MODE_NOTE);
                    // Disable the app op.
                    mAppOps.setMode(permission.getOp(), uid, mPackageInfo.packageName, AppOpsManager.MODE_NOTE);
                }

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

                // Update the permission flags.
                if (!fixedByTheUser) {
                    // Now the apps can ask for the permission as the user
                    // no longer has it fixed in a denied state.
                    if (permission.isUserFixed() || permission.isUserSet()) {
                        permission.setUserFixed(false);
                        permission.setUserSet(false);
                        mPackageManager.updatePermissionFlags(permission.getName(), mPackageInfo.packageName,
                                PackageManager.FLAG_PERMISSION_USER_FIXED | PackageManager.FLAG_PERMISSION_USER_SET, 0,
                                mUserHandle);
                    }
                }
            } else {
                // Legacy apps cannot have a not granted permission but just in case.
                if (!permission.isGranted()) {
                    continue;
                }

                int killUid = -1;
                int mask = 0;

                // If the permissions has no corresponding app op, then it is a
                // third-party one and we do not offer toggling of such permissions.
                if (permission.hasAppOp()) {
                    if (!permission.isAppOpNoted()) {
                        permission.setAppOpMode(AppOpsManager.MODE_NOTE);
                        // Enable the app op.
                        mAppOps.setMode(permission.getOp(), uid, mPackageInfo.packageName, AppOpsManager.MODE_NOTE);
                    }

                    // Mark that the permission should not be be granted on upgrade
                    // when the app begins supporting runtime permissions.
                    if (permission.shouldRevokeOnUpgrade()) {
                        permission.setRevokeOnUpgrade(false);
                        mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
                    }
                }

                // Granting a permission explicitly means the user already
                // reviewed it so clear the review flag on every grant.
                if (permission.isReviewRequired()) {
                    permission.resetReviewRequired();
                    mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
                }

                if (mask != 0) {
                    mPackageManager.updatePermissionFlags(permission.getName(), mPackageInfo.packageName, mask, 0,
                            mUserHandle);
                }

                if (killUid != -1) {
                    mActivityManager.killUid(killUid, KILL_REASON_APP_OP_CHANGE);
                }
            }
        }

        return true;
    }
2、AMS中checkPermission 加入的处理流程(会出现死锁,使用M 系统源生即可)
    /**
     * added by wj, check permission of application which the target sdk is less than the one of M and
     * the protection level of permission is dangerous.
     * @return 0 or 1 indicates the permission has handled by AppOps.
     */
    private final Object aLock = new Object();
    private int checkPermissionForAppOps(String permission, int uid) {
        PermissionInfo info = null;
        try {
            info = mContext.getPackageManager().getPermissionInfo(permission, 0);
        } catch (PackageManager.NameNotFoundException e) {
            //ignore
        }

        //At first, the permission is dangerous.
        if (info == null
            || ((info.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) != PermissionInfo.PROTECTION_DANGEROUS))
            return -1;

        String pkgName = null;
        try {
            pkgName = AppGlobals.getPackageManager().getNameForUid(uid);
        } catch (RemoteException e) {
            //ignore
        }
        Slog.d(TAG, "checkPermissionForAppOps, permission = " + permission + ", uid = " + uid
            + ", packageName = " + pkgName);

        if (pkgName == null)
            return -1;

        ApplicationInfo ai = null;
        try {
            ai = mContext.getPackageManager().getApplicationInfo(pkgName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            //ignore
        }

        if (ai == null || ai.targetSdkVersion >= Build.VERSION_CODES.M)
            return -1;
        Slog.d(TAG, "checkPermissionForAppOps, targetSdkVersion = " + ai.targetSdkVersion);

        synchronized(aLock) {
            AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
            int tUid = Binder.getCallingUid() >= uid ? Binder.getCallingUid() : uid;//get application uid but not the one from native
            if ((tUid >= Process.FIRST_APPLICATION_UID)
                && (pkgName.indexOf("android.uid.systemui") != 0)
                && (pkgName.indexOf("android.uid.system") != 0)) {
                int result = mAppOpsManager.noteOp(AppOpsManager.permissionToOp(permission),
                    tUid, pkgName);
                return result;
            }
        }

        return -1;
    }

3、加入MODE_NOTED 之后的界面


4、权限控制选择界面


5、对话框提示界面




猜你喜欢

转载自blog.csdn.net/jingerppp/article/details/80674768
M
^M