Android权限管理原理

Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在Release版本中都是把这个功能给隐藏掉的。在6.0之后,Google为了简化安装流程且方便用户控制权限,正式引入了runtime-permission,允许用户在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,并且在相应的时机动态申请权限,在适配了Android6.0的App运行在Android 6.0+的手机上时,就会调用6.0相关的API,不过在低版本的手机上,仍然是按安装时权限处理。

AppOpsManager动态权限管理:官方预演的权限管理

AppOpsManager是Google在Android4.3引入的动态权限管理方式,不过,Google觉得不成熟,所以在每个发行版的时候,总是会将这个功能给屏蔽掉。该功能跟国内的权限动态管理表现类似,这里用CyanogenMod12里面的实现讲述一下,(国内的ROM源码拿不到,不过从表现来看,实现应该类似)。AppOpsManager实现的动态管理的本质是:将鉴权放在每个服务内部,比如,如果App要申请定位权限,定位服务LocationManagerService会向AppOpsService查询是否授予了App定位权限,如果需要授权,就弹出一个系统对话框让用户操作,并根据用户的操作将结果持久化在文件中,如果在Setting里设置了响应的权限,也会去更新相应的权限操作持久化文件/data/system/appops.xml,下次再次申请服务的时候,服务会再次鉴定权限。

举个栗子-定位服务LocationManagerService: CM12源码

App在使用定位服务的时候,一般是通过LocationManager的requestLocationUpdates获取定位,其实是通过Binder请求LocationManagerService去定位。

/android/location/LocationManager.java

private void requestLocationUpdates(LocationRequest request, LocationListener listener,
        Looper looper, PendingIntent intent) {
     ...
    try {
        mService.requestLocationUpdates(request, transport, intent, packageName);
     ...

/com/android/server/LocationManagerService.java

@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
        PendingIntent intent, String packageName) {
    if (request == null) request = DEFAULT_LOCATION_REQUEST;
    checkPackageName(packageName);
    <!--关键函数 1 ,查询Manifest文件,是否进行了权限声明 -->
    int allowedResolutionLevel = getCallerAllowedResolutionLevel();
    checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
            request.getProvider());
    。。。
    <!--获取调用app的pid跟uid-->
    final int pid = Binder.getCallingPid();
    final int uid = Binder.getCallingUid();
    // providers may use public location API's, need to clear identity
    long identity = Binder.clearCallingIdentity();
    try {
    <!--关键函数 2 检查是否动态授权了权限,或者拒绝了权限-->
        checkLocationAccess(uid, packageName, allowedResolutionLevel);

        synchronized (mLock) {
            Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid,
                    packageName, workSource, hideFromAppOps);
            if (receiver != null) {
                    requestLocationUpdatesLocked(sanitizedRequest, receiver, pid,
                                                 uid, packageName);
            }
        }
    } finally {
        Binder.restoreCallingIdentity(identity);
    }
}

getCallerAllowedResolutionLevel主要通过调用getAllowedResolutionLevel查询APP是否在Manifest中进行了声明

private int getCallerAllowedResolutionLevel() {
    return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}

 private int getAllowedResolutionLevel(int pid, int uid) {
     if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
             pid, uid) == PackageManager.PERMISSION_GRANTED) {
         return RESOLUTION_LEVEL_FINE;
     } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
             pid, uid) == PackageManager.PERMISSION_GRANTED) {
         return RESOLUTION_LEVEL_COARSE;
     } else {
         return RESOLUTION_LEVEL_NONE;
     }
 }

checkLocationAccess这里才是动态鉴权的入口,在checkLocationAccess函数中,会调用mAppOps.checkOp去鉴权,mAppOps就是AppOpsManager实例,

boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {
    int op = resolutionLevelToOp(allowedResolutionLevel);
    if (op >= 0) {
        int mode = mAppOps.checkOp(op, uid, packageName);
        if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_ASK ) {
            return false;
        }
    }
    return true;
}

进而通过Binder向AppOpsService服务发送鉴权请求

 public int noteOp(int op, int uid, String packageName) {
    try {
        int mode = mService.noteOperation(op, uid, packageName);
        if (mode == MODE_ERRORED) {
            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
        }
        return mode;
    } catch (RemoteException e) {
    }
    return MODE_IGNORED;
}

AppOpsService负责动态权限的鉴定跟更新,接着看noteOperation代码

@Override
public int noteOperation(int code, int uid, String packageName) {
    final Result userDialogResult;
    verifyIncomingUid(uid);
    verifyIncomingOp(code);
    synchronized (this) {
        Ops ops = getOpsLocked(uid, packageName, true);
        ...
          <!--关键点 1-->
        if (switchOp.mode == AppOpsManager.MODE_IGNORED ||
            switchOp.mode == AppOpsManager.MODE_ERRORED) {

            op.rejectTime = System.currentTimeMillis();
            op.ignoredCount++;
            return switchOp.mode;
           <!--关键点 2-->
        } else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) {

            op.time = System.currentTimeMillis();
            op.rejectTime = 0;
            op.allowedCount++;
            return AppOpsManager.MODE_ALLOWED;
        } else {
            op.noteOpCount++;
            <!--关键函数 3-->
            userDialogResult = askOperationLocked(code, uid, packageName,
                switchOp);
        }
    }
    return userDialogResult.get();
}

在上面的代码里面,1、2是对已经处理过的场景直接返回已授权,或者已经拒绝,而3就是我们常见授权入口对话框,这里是统一在AppOpsServie中进行授权处理的。askOperationLocked会显示一个系统对话框,用户选择授权或者拒绝后,AppOpsServie会将选择记录在案,并通知申请服务提供或者拒绝服务。askOperationLocked通过mHandler发送鉴权Message,看一下实现其实就是新建了一个PermissionDialog授权对话框,并且将AppOpsService的引用传了进去,授权后会通过mService.notifyOperation通知授权结果。

  mHandler = new Handler() {
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case SHOW_PERMISSION_DIALOG: {
                    HashMap<String, Object> data =
                        (HashMap<String, Object>) msg.obj;
                    synchronized (this) {
                        Op op = (Op) data.get("op");
                        Result res = (Result) data.get("result");
                        op.dialogResult.register(res);
                        if(op.dialogResult.mDialog == null) {
                            Integer code = (Integer) data.get("code");
                            Integer uid  = (Integer) data.get("uid");
                            String packageName =
                                (String) data.get("packageName");
                            Dialog d = new PermissionDialog(mContext,
                                AppOpsService.this, code, uid,
                                packageName);
                            op.dialogResult.mDialog = (PermissionDialog)d;
                            d.show();
                        }
                    }
                }break;
                }
            }
        };
AppOpsManager动态权限管理流程

Android发行版源码对于动态权限管理的支持(几乎为零)

在Android4.3到5.1之间,虽然App可以获得AppOpsManager的实例,但是真正动态操作权限的接口setMode却被隐藏,如下

/** @hide */
public void setMode(int code, int uid, String packageName, int mode) {
    try {
        mService.setMode(code, uid, packageName, mode);
    } catch (RemoteException e) {
    }
}

遍历源码也只有NotificationManagerService这个系统应用使用了setMode,也就是说发行版,只有通知是通过系统的通知管理进行动态管理的。

public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
    checkCallerIsSystem();

    Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);

    mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
            enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);

    // Now, cancel any outstanding notifications that are part of a just-disabled app
    if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
        cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
    }
}

对于6.0以下的手机权限如何检测

对于Android6.0以下的手机,不需要关心targetVersion。先说个自己验证的结果:基本没法检测,同时也不需要检测,就算检测出来也没有多大意义,因为,触发时机是在真正的调用服务时候。对于4.3到6.0之前的国产ROM,虽然采用AppopsManagerService,但是并未按照Google的模型对所有权限进行适配,在这个模型下,也就适配了两个权限,(悬浮窗还不一定有)

  • 通知权限 public static final int OP_POST_NOTIFICATION = 11;
  • 悬浮窗权限 public static final int OP_SYSTEM_ALERT_WINDOW = 24;

Google发行版的APPOpsService,基本是把整个鉴权逻辑给屏蔽了,通过CM的源码,课对这部分代码窥探一斑,如果整个权限都采用4.3权限管理模型,在拒绝一项权限的时候,这个操作会被持久化到appops.xml中去,但是具体看下去,其实并不是如此,这种机制只对以上两个权限生效:

    <pkg n="com.xxx">
    <uid n="10988">
    <!--关键点1-->
    <op n="11" m="1" t="1513145979969" r="1521550658067" />
    <op n="12" t="1521550651593" />
    <op n="29" t="1521550682769" />

    <pkg n="com.wandoujia.phoenix2.usbproxy">
    <uid n="10969">
    <op n="4" t="1517279031173" />
     <!--关键点2-->
    <op n="11" m="1" t="1510889291834" r="1517279030708" />
    <op n="14" t="1517293452801" />
    <!--关键点3-->
    <op n="24" m="1" />
    <op n="40" t="1513599239364" d="600011" />

国产rom中,假如你拒绝授权位置权限,按照AppOpsService模型,该操作应该被持久化到appops.xml中去,但是,结果并非如此,也就是说,对于其他权限,国产ROM应该是自己糊弄了一套持久管理,持久化Android系统API无法访问的地方,仅仅为自身ROM可见。appops.xml真正被系统使用时从Android6.0开始,其实Android6.0是有两套权限管理的,这其实很混乱,不知道Google怎么想的,不过6.0似乎也有漏洞:权限的授予跟回收权限好像并不配对

那么这就带来了一个问题,在Android4.3到Android6.0之间的版本,并没有同一个API来检测是否获取了某种权限,因为你动态更新的权限并未持久化到appops.xml中去。对于Android6.0之前的ROM,虽然不能检测,但完全可以直接用服务,不会崩溃,因为如果真需要鉴权,它的鉴权时机其实是在服务使用的时候。AppopsManager在6.0之前,只能用来检测通知,可能还有悬浮窗。

Android 6.0权限管理原理

Android6.0的runtime-permission机制让用户在任何时候都可以取消授权,因此,每次在申请系统服务的时候,都要动态查询是否获取了相应的权限,如果没有获取,就需要动态去申请,首先先看一下权限的查询:

Android6.0权限查询

support-v4兼容包里面提供了一个工具类PermissionChecker,可以用来检查权限获取情况。

PermissionChecker

public static int checkPermission(@NonNull Context context, @NonNull String permission,
        int pid, int uid, String packageName) {
    if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
        return PERMISSION_DENIED;
    }

    String op = AppOpsManagerCompat.permissionToOp(permission);
    if (op == null) {
        return PERMISSION_GRANTED;
    }

    if (packageName == null) {
        String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
        if (packageNames == null || packageNames.length <= 0) {
            return PERMISSION_DENIED;
        }
        packageName = packageNames[0];
    }

    if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
            != AppOpsManagerCompat.MODE_ALLOWED) {
        return PERMISSION_DENIED_APP_OP;
    }

    return PERMISSION_GRANTED;
}

这里我们只关心context.checkPermission,从上面对于4.3-5.1的APPOpsManager的分析,我们知道AppOpsManagerCompat本身的一些操作对于权限管理并没有实际意义,只是用来做一些标记,最多就是对于通知权限有些用,接下来看checkPermission:

ContextImple.java

/** @hide */
@Override
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
    if (permission == null) {
        throw new IllegalArgumentException("permission is null");
    }
    try {
        return ActivityManagerNative.getDefault().checkPermissionWithToken(
                permission, pid, uid, callerToken);
    } catch (RemoteException e) {
        return PackageManager.PERMISSION_DENIED;
    }
}

接着往下看

ActivityManagerNative.java

public int checkPermission(String permission, int pid, int uid)
        throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    data.writeString(permission);
    data.writeInt(pid);
    data.writeInt(uid);
    mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0);
    reply.readException();
    int res = reply.readInt();
    data.recycle();
    reply.recycle();
    return res;
}

ActivityManagerService

public int checkPermission(String permission, int pid, int uid) {
    if (permission == null) {
        return PackageManager.PERMISSION_DENIED;
    }
    return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
}

进而调用ActivityManager.checkComponentPermission,调用AppGlobals.getPackageManager().checkUidPermission(permission, uid);

ActivityManager.java

/** @hide */
public static int checkComponentPermission(String permission, int uid,
        int owningUid, boolean exported) {
    // Root, system server get to do everything.
    
    <!--root及System能获取所有权限-->
    if (uid == 0 || uid == Process.SYSTEM_UID) {
        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;
}

最终调用PackageManagerService.java去查看是否有权限,到这里,我们只需要知道权限的查询其实是通过PKMS来进行的。心里先有个底,权限的更新,持久化,恢复都是通过PKMS来进行的。

PKMS不同版本的权限查询

Android5.0的checkUidPermission

 public int checkUidPermission(String permName, int uid) {
        final boolean enforcedDefault = isPermissionEnforcedDefault(permName);
        synchronized (mPackages) {
        <!--PackageManagerService.Setting.mUserIds数组中,根据uid查找uid(也就是package)的权限列表-->
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                GrantedPermissions gp = (GrantedPermissions)obj;
                if (gp.grantedPermissions.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
            <!--mSystemPermissions记录一些系统级的应用的 uid 对应的 permission->
                HashSet<String> perms = mSystemPermissions.get(uid);
                if (perms != null && perms.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            }
            if (!isPermissionEnforcedLocked(permName, enforcedDefault)) {
                return PackageManager.PERMISSION_GRANTED;
            }
        }
        return PackageManager.PERMISSION_DENIED;
    }

Android6.0+的checkUidPermission

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

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

        synchronized (mPackages) {
            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;
                }
            } 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;
    }

可以看到Android6.0之后,对权限的操作是PermissionsState

PermissionsState.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)

public boolean hasPermission(String name, int userId) {
    enforceValidUserId(userId);

    if (mPermissions == null) {
        return false;
    }

    PermissionData permissionData = mPermissions.get(name);
    return permissionData != null && permissionData.isGranted(userId);
}

从上面的代码可以很清晰看出,6.0之后,除了声明了权限之外,还必须是授权了的。运行时权限跟install权限有所不同,对于install权限isGranted一直返回是True,这里先不必深究PermissionsState是怎么存进内存,先记住,后面会将讲。

权限检查流程

Android6.0动态申请权限

申请权限可以通过V4包里面的ActivityCompat,它已经对不同版本做了兼容

ActivityCompat.java

 public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final int requestCode) {
        if (Build.VERSION.SDK_INT >= 23) {
            ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);
        } else if (activity instanceof OnRequestPermissionsResultCallback) {
        
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    final int[] grantResults = new int[permissions.length];

                    PackageManager packageManager = activity.getPackageManager();
                    String packageName = activity.getPackageName();

                    final int permissionCount = permissions.length;
                    for (int i = 0; i < permissionCount; i++) {
                        grantResults[i] = packageManager.checkPermission(
                                permissions[i], packageName);
                    }

                    ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
                            requestCode, permissions, grantResults);
                }
            });
        }
    }

可以看到,如果是6.0以下,直接通过PKMS查询是否在Manifest里面申请了权限,并把查询结果通过onRequestPermissionsResult回调传给Activity或者Fragment。其实这里只要在Manifest中声明了,就会默认是Granted。接着往下看:ActivityCompatApi23最终会调用activity.requestPermissions去请求权限。

Activity

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}

Intent其实是通过PackageManager(ApplicationPackageManager实现类)获取的Intent

    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;
}

这里首先是隐式的获取授权Activity组件相关信息(GrantPermissionsActivity),其实就是对话框样式的授权Activity,它是PackageInstaller系统应用里面的一个Activity。这里的getPermissionControllerPackageName其实就是获取相应的包名,

ApplicationPackageManager.java (android-6.0\frameworks\base\core\java\android\app)

@Override
public String getPermissionControllerPackageName() {
    synchronized (mLock) {
        if (mPermissionsControllerPackageName == null) {
            try {
                mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
            } catch (RemoteException e) {
                throw new RuntimeException("Package manager has died", e);
            }
        }
        return mPermissionsControllerPackageName;
    }
}

最终通过PackageManagerService获取包名

PackageManagerService.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)

@Override
public String getPermissionControllerPackageName() {
    synchronized (mPackages) {
        return mRequiredInstallerPackage;
    }
}

mRequiredInstallerPackage这个变量具体赋值是在PMS的构造器中:对于原生Android 6.0,权限管理的APP跟安装器是同一个

mRequiredInstallerPackage = getRequiredInstallerLPr();

这里会得到PackageInstaller应用的相关信息,PackageInstaller负责应用的安装与卸载,里面还包含了对授权管理的一些逻辑。startActivityForResult启动的就是PackageInstaller中的GrantPermissionsActivity,该Activity主要负责权限的授予工作。

    <activity android:name=".permission.ui.GrantPermissionsActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:excludeFromRecents="true"
            android:theme="@style/GrantPermissions">
        <intent-filter>
            <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>

这是一个类似于对话框的悬浮窗样式的Activity

<style name="GrantPermissions" parent="Settings">
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowElevation">@dimen/action_dialog_z</item>
    <item name="android:windowSwipeToDismiss">false</item>
</style>

之后就是动态更新权限流程:

权限申请流程

如何动态更新RuntimePermission

通过上面的流程,我们进入了GrantPermissionsActivity,在这个Activity里面,如果一开始没有获得权限,就会弹出权限申请对话框,根据用户的操作去更新PKMS中的权限信息,同时将更新的结构持久化到runtime-permissions.xml中去。

GrantPermissionsActivity

GrantPermissionsActivity其实是利用GroupState对象与PKMS通信,远程更新权限的,当然,如果权限都已经授予了,那么就不需要再次弹出权限申请对话框。

public class GrantPermissionsActivity extends OverlayTouchActivity
        implements GrantPermissionsViewHandler.ResultListener {
        
    private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>();
    ....
    
    @Override
    public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
        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();
        }
    }

具体更新流程:

public boolean grantRuntimePermissions(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;
        }
            ...
            <!--一些关键点-->

            // 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);

可以看到最终还是调用PackageManager去更新App的运行时权限,最终走进PackageManagerService服务,

PackageManagerService

 @Override
    public void grantRuntimePermission(String packageName, String name, final int userId) {
        if (!sUserManager.exists(userId)) {
            Log.e(TAG, "No such user:" + userId);
            return;
        }
        
        ...一些检查
        
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
                "grantRuntimePermission");

        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                true /* requireFullPermission */, true /* checkShell */,
                "grantRuntimePermission");
                。。。。。
                ...
            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
            sb = (SettingBase) pkg.mExtras;
            if (sb == null) {
                throw new IllegalArgumentException("Unknown package: " + packageName);
            }

            final PermissionsState permissionsState = sb.getPermissionsState();
              
              ...
              ...授权
            
            final int result = permissionsState.grantRuntimePermission(bp, userId);
            switch (result) {
                case PermissionsState.PERMISSION_OPERATION_FAILURE: {
                    return;
                }

                case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
                        }
                    });
                }
                break;
            }

            mOnPermissionChangeListeners.onPermissionsChanged(uid);
            
            
            <!--持久化-->  
            
            // Not critical if that is lost - app has to request again.
            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
        }
 private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(PackageParser.Package pkg,
        BasePermission bp) {
    int index = pkg.requestedPermissions.indexOf(bp.name);
    if (index == -1) {
        throw new SecurityException("Package " + pkg.packageName
                + " has not requested permission " + bp.name);
    }
    if (!bp.isRuntime() && !bp.isDevelopment()) {
        throw new SecurityException("Permission " + bp.name
                + " is not a changeable permission type");
    }
}

首先要更新内存中的权限授予情况

PermissionsState.java

 private int grantPermission(BasePermission permission, int userId) {
    if (hasPermission(permission.name, userId)) {
        return PERMISSION_OPERATION_FAILURE;
    }

    final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
    final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;

    PermissionData permissionData = ensurePermissionData(permission);

    if (!permissionData.grant(userId)) {
        return PERMISSION_OPERATION_FAILURE;
    }

    if (hasGids) {
        final int[] newGids = computeGids(userId);
        if (oldGids.length != newGids.length) {
            return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
        }
    }

    return PERMISSION_OPERATION_SUCCESS;
}
    private PermissionData ensurePermissionData(BasePermission permission) {
    if (mPermissions == null) {
        mPermissions = new ArrayMap<>();
    }
    PermissionData permissionData = mPermissions.get(permission.name);
    if (permissionData == null) {
        permissionData = new PermissionData(permission);
        mPermissions.put(permission.name, permissionData);
    }
    return permissionData;
}

下一步,要将更新的权限持久化到文件中去 mSettings.writeRuntimePermissionsForUserLPr

RuntimePermission持久化

Settings.java

 public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
    if (sync) {
        mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
    } else {
        mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
    }
}

Settings.getPackageLPw这个方法,这是在安装应用扫描的时候scanPackageDirtyLI方法调用的,里面可以看到Settings类中的mUserIds、mPackages里面存的value还有PackageManagerService中的mPackages.pkg. mExtras都是同一个玩意奏是个PackageSetting。

  private File getUserRuntimePermissionsFile(int userId) {
    // TODO: Implement a cleaner solution when adding tests.
    // This instead of Environment.getUserSystemDirectory(userId) to support testing.
    File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
    return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
}

在目录data/system/0/runtime-permissions.xml存放需要运行时申请的权限,Android6.0以上才有

  <pkg name="com.snail.labaffinity">
    <item name="android.permission.CALL_PHONE" granted="true" flags="0" />
    <item name="android.permission.CAMERA" granted="false" flags="1" />
  </pkg>
权限更新及持久化

RuntimePermission恢复(其实这里也包含普通权限)

这些持久化的数据会在手机启动的时候由PMS读取,开机启动,PKMS扫描Apk,并更新package信息,检查/data/system/packages.xml是否存在,这个文件是在解析apk时由writeLP()创建的,里面记录了系统的permissions,以及每个apk的name,codePath,flags,ts,version,uesrid等信息,这些信息主要通过apk的AndroidManifest.xml解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机直接从里面读取相关信息添加到内存相关列表中,当有apk升级,安装或删除时会更新这个文件,packages.xml放的只包括installpermission,runtimepermissiono由runtime-permissions.xml存放。

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    ....
    mSettings = new Settings(mPackages);
    
          //汇总并更新和Permission相关的信息

  updatePermissionsLPw(null, null, true,

                           regrantPermissions,regrantPermissions);

   //将信息写到package.xml、package.list及package-stopped.xml文件中
   mSettings.writeLPr();
   
    ....
    mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

Settings(File dataDir, Object lock) {

    mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

<!--加载package信息-->

根据SettingsFile或者BackupSettingsFile读取相应的设置信息 生成PackageSetting对象,里面有权限列表字段protected final PermissionsState mPermissionsState;,之后再运行中,动态权限的操作都是针对这个对象

boolean readLPw(@NonNull List<UserInfo> users) {
    FileInputStream str = null;
    if (mBackupSettingsFilename.exists()) {
        try {
            str = new FileInputStream(mBackupSettingsFilename);
            mReadMessages.append("Reading from backup settings file\n");
     ...
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
        
            String tagName = parser.getName();
            if (tagName.equals("package")) {
            
       !--读取package信息,包括install权限信息(对于Android6.0package.xml)-->
    
    readPackageLPw(parser); 
        ...
        
      <!--读取runtime权限信息-->
      
    for (UserInfo user : users) {
        mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
    }
}


private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
    String name = null;
     ...
    (tagName.equals(TAG_PERMISSIONS)) {
            readInstallPermissionsLPr(parser,
                        packageSetting.getPermissionsState());

之后就可以checkpermission了

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

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

        synchronized (mPackages) {
            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;
                }
权限恢复流程

原来的权限存放位置在哪?不会都从Android Manifest清单去读取,只会在启动时读取一次。Android6.0之前会吧所有的权限都放置在data/system/packages.xml文件中。Android6.0之后,分为运行时权限跟普通权限,普通权限还是放在data/system/packages.xml中,运行时权限防止在data/system/users/0/runtime-permissions.xml文件中。根据运行时是否动态申请去更新权限。

Android6.0申请普通权限会怎么样

Android6.0里,普通权限仍然按照运行时权限的模型,只是granted="true",就是永远是取得授权的。所以可以直接获得权限申请成功的回调。如果查看packages.xml,就会发现:如下信息:

<perms>
    <item name="android.permission.INTERNET" granted="true" flags="0" />
    <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
</perms>

Android的关键节点,在哪里?

关键节点并不是查询是否具有该权限,Android6.0之前的 权限查询是不会触发权限申请与授权的,只有在请求系统服务的时候,由系统服务调用AppopsManager去查询是否赋予了该权限,第一次未操作肯定是null,未赋予就可能会触发权限申请逻辑,这个点在各个系统服务内部,由AppOpsService服务统一管理,不过对于官方的Release版本,其实只有系统通知APP才有动态权限管理的能力,其他都没有操作能力。

作者:看书的小蜗牛
原文链接: Android权限管理原理

参考文档

1、Android 安全機制概述 Permission
2、android permission权限与安全机制解析
3、android6.0权限管理原理
4、深入理解 PackageManagerService
5、Android 4.3 隐藏功能 App Ops 分析
6、Android 权限机制,你真的了解吗?
7、Android原生权限管理:AppOps
8、 Android 5.1 AppOps总结
9、CM12源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,242评论 5 461
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,138评论 2 372
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,412评论 0 323
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,380评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,221评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,203评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,607评论 3 384
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,292评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,581评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,650评论 2 311
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,405评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,268评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,646评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,942评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,226评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,601评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,795评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,098评论 25 707
  • 我不害怕老去 而是害怕老去的路上 每个虚度的自己 虚度的自己 在晚风里追忆 追忆意气风发的曾经 夏虫也为我悲鸣 悲...
    烟雨心清阅读 551评论 3 9
  • 最佳药引:苍蝇的挣扎,有你也有他 十一假期回老家休息,有天,晴空万里,闲来无事,盯着面前一张黏苍蝇贴纸观...
    紫竹阅读 456评论 14 6
  • 看《萤火虫小巷》,说闺中密友。 第一部分写七十年代“舞后”。读起来有种看好莱坞励志友情电影,一位美丽无双却父母不爱...
    最近想努力记录阅读 472评论 0 0
  • 睡不着睡不着睡不着!!!!你知道吗?你知道吗?你知道吗?
    萍心波动阅读 244评论 1 0