Android6.0, permissions are divided into the install permissions and runtime permissions, if our targetSdkVersion>=23, install permissions and Runtime permissions are separate, app has been adapted for 6.0, no big problem, It works on older or later phones, which Google recommends as an adaptation. However, if targetSdkVersion < 23, you will encounter some problems on phones after 6.0, because in this case the default permissions are granted in full, but may be manually cancelled by the user, and the Context’s checkSelfPermission interface will fail. After API 6.0, app is intalled with permission, and its granted value is always true when targetSdkVersion < 23. After API 6.0, app is intalled with permission, and its granted value is always true. The Context checkSelfPermission interface uses the granted value of GRANTED as the reference for authorization. If you use this interface, the granted permission of the installed permission will not be affected. When targetSdkVersion < 23, the permissions in the package information include all permissions applied by app.

<package name="com.snail.labaffinity" codePath="/data/app/com.snail.labaffinity-1" nativeLibraryPath="/data/app/com.snail.labaffinity-1/lib" publicFlags="944291398" privateFlags="0" ft="15f0f58e548" it="15f0f58e548" ut="15f0f58e548" version="1" userId="10084">
    <perms>
        <item name="android.permission.ACCESS_FINE_LOCATION" granted="true" flags="0" />
        <item name="android.permission.INTERNET" granted="true" flags="0" />
        <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
        <item name="android.permission.ACCESS_COARSE_LOCATION" granted="true" flags="0" />
        <item name="android.permission.READ_PHONE_STATE" granted="true" flags="0" />
        <item name="android.permission.CALL_PHONE" granted="true" flags="0" />
        <item name="android.permission.CAMERA" granted="true" flags="0" />
        <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
        <item name="android.permission.READ_CONTACTS" granted="true" flags="0" />
    </perms>
    <proper-signing-keyset identifier="18" />
</package>
Copy the code

The checkSelfPermission method of the targetSdkVersion < 23 Context fails. The checkSelfPermission method of the targetSdkVersion < 23 Context fails. How to determine whether the 6.0 mobile phone is authorized.

Why checkSelfPermission of targetSdkVersion < 23 Context is invalid

The Context checkSelfPermission will be called, and ContextImp’s checkPermission will be called

@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; }}Copy the code

The checkPermission of ActivityManagerService is requested, and the checkUidPermission of PackageManagerService is called after preprocessing and forwarding

@Override public int checkUidPermission(String permName, int uid) { final int userId = UserHandle.getUserId(uid); synchronized (mPackages) { <! Object obj = msettings.getUseridlpr (userhandle.getAppId (uid)); if (obj ! = null) { final SettingBase ps = (SettingBase) obj; final PermissionsState permissionsState = ps.getPermissionsState(); <! - inspection authorization - > if (permissionsState hasPermission (permName, userId)) {return PackageManager. PERMISSION_GRANTED; } if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) { return PackageManager.PERMISSION_GRANTED; }}... } return PackageManager.PERMISSION_DENIED; }Copy the code

The PackageManagerService obtains permissions from the mSettings global variable and further verifies that permissions are granted

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); }Copy the code

All permissions for targetSdkVersion<23 are in packages. XML. Grante is always true and cannot be updated. Have a look at 6.0 after authorization and cancel the authorization function, first take a look at a variable mAppSupportsRuntimePermissions

    mAppSupportsRuntimePermissions = packageInfo.applicationInfo
            .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
    mAppOps = context.getSystemService(AppOpsManager.class);
Copy the code

MAppSupportsRuntimePermissions defined in AppPermissionGroup, 6.0 after the permissions are grouped, for targetSdkVersion < 23 APP, is obviously does not support dynamic authority management, Then the authorization and disauthorization functions are very different as follows: the authorization function

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { final int uid = mPackageInfo.applicationInfo.uid; for (Permission permission : mPermissions.values()) { if (filterPermissions ! = null && ! ArrayUtils.contains(filterPermissions, permission.getName())) { continue; } <! -- Key point 1 if supported, Namely targetSdkVersion > 23 that walk 6.0 dynamic permissions management that a set - > 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); <! - 2 key updates its runtime - permission. In the XML granted value - > mPackageManager. GrantRuntimePermission (mPackageInfo packageName, permission.getName(), mUserHandle); }... } else { if (! permission.isGranted()) { continue; } int killUid = -1; int mask = 0; if (permission.hasAppOp()) { if (! permission.isAppOpAllowed()) { permission.setAppOpAllowed(true); <! -- Keypoint 3 set appopsManager.mode_allowed --> mAppOps. SetUidMode (permission. GetAppOp (), uid, appopsManager.mode_allowed); killUid = uid; }} <! Update PermissionFlags--> if (mask! = 0) { mPackageManager.updatePermissionFlags(permission.getName(), mPackageInfo.packageName, mask, 0, mUserHandle); } } } return true; }Copy the code

If targetSdkVersion>=23 supports dynamic permission management, update the dynamic permission and persist it to runtime-permissibility. XML. If targetSdkVersion<23 is not known for dynamic management of 6.0, then only update AppOps. This is the old dynamic permission management model introduced in 4.3, but here permissions are persisted to AppOps. XML. This flag can be used to specify whether appops. XML is authorized or not (for targetSdkVersion<23). RevokeRuntimePermissions revokeRuntimePermissions revokeRuntimePermissions revokeRuntimePermissions revokeRuntimePermissions How to check the permissions of targetSdkVersion<23 app on mobile phones above 6.0? Google has given a compatible class, PermissionChecker, which indirectly uses AppOpsService logic to determine whether permissions are granted.

When targetSdkVersion < 23, how to determine whether the 6.0 phone is authorized

When targetSdkVersion < 23, the 6.0 permission checking API is invalid. However, the above analysis guidance will still be stored and persisted in the appops. XML file. AppOpsService can be seen as an additional permission management model retained in 6.0 for compatibility with older apps. In 6.0 systems, it can be seen as a supplement to runtime permission management. In fact, AppOpsService was introduced in 4.3, but it is not flexible and has little effect. Previously, only notification management was used. Take a look at what PermissionChecker, a compatible class provided by Google, does:

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 you can cancel the authorization, see is it is in a state of permissions allowed, if not, that is, users take the initiative to shut down the permissions - > if (AppOpsManagerCompat. NoteProxyOp (context, op, packageName)! = AppOpsManagerCompat.MODE_ALLOWED) { return PERMISSION_DENIED_APP_OP; } return PERMISSION_GRANTED; }Copy the code

For 6.0 after phone AppOpsManagerCompat. NoteProxyOp invokes AppOpsManager23 noteProxyOp,

private static class AppOpsManagerImpl { public String permissionToOp(String permission) { return null; } public int noteOp(Context context, String op, int uid, String packageName) { return MODE_IGNORED; } public int noteProxyOp(Context context, String op, String proxiedPackageName) { return MODE_IGNORED; } } private static class AppOpsManager23 extends AppOpsManagerImpl { @Override public String permissionToOp(String permission) { return AppOpsManagerCompat23.permissionToOp(permission); } @Override public int noteOp(Context context, String op, int uid, String packageName) { return AppOpsManagerCompat23.noteOp(context, op, uid, packageName); } @Override public int noteProxyOp(Context context, String op, String proxiedPackageName) { return AppOpsManagerCompat23.noteProxyOp(context, op, proxiedPackageName); }}Copy the code

The above was 6.0 before the corresponding API, the following is after 6.0 and its corresponding interface, AppOpsManagerCompat23. NoteProxyOp will further call AppOpsManager noteProxyOp send AppOpsService request

public static int noteProxyOp(Context context, String op, String proxiedPackageName) {
    AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
    return appOpsManager.noteProxyOp(op, proxiedPackageName);
}
Copy the code

Finally, how does AppOpsService check permissions

private int noteOperationUnchecked(int code, int uid, String packageName, int proxyUid, String proxyPackageName) { synchronized (this) { Ops ops = getOpsLocked(uid, packageName, true); Op op = getOpLocked(ops, code, true); if (isOpRestricted(uid, code, packageName)) { return AppOpsManager.MODE_IGNORED; } op.duration = 0; final int switchCode = AppOpsManager.opToSwitch(code); UidState uidState = ops.uidState; if (uidState.opModes ! = null) { final int uidMode = uidState.opModes.get(switchCode); op.rejectTime = System.currentTimeMillis(); return uidMode; } } final Op switchOp = switchCode ! = code ? getOpLocked(ops, switchCode, true) : op; if (switchOp.mode ! = AppOpsManager.MODE_ALLOWED) { op.rejectTime = System.currentTimeMillis(); return switchOp.mode; } op.time = System.currentTimeMillis(); op.rejectTime = 0; op.proxyUid = proxyUid; op.proxyPackageName = proxyPackageName; return AppOpsManager.MODE_ALLOWED; }}Copy the code

UidState can be regarded as the permission model corresponding to each application. Part of the data here is recovered from appops. XML, and part of the data is added when the permission is updated. Appops. XML is not persisted until 30 minutes after the phone’s permissions have been updated. The data in this file is restored at startup time, and the constructor of ActivityManagerService is always started:

public ActivityManagerService(Context systemContext) { ... mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler); . }Copy the code

In the constructor of AppOpsService, permissions persisted in appops. XML are retrieved and stored in memory.

public AppOpsService(File storagePath, Handler handler) { mFile = new AtomicFile(storagePath); mHandler = handler; // readState() is read when it is created; }Copy the code

ReadState is to re-read the persistent UidState data, and the following mFile is actually appops. XML file object

void readState() { synchronized (mFile) { synchronized (this) { FileInputStream stream; try { stream = mFile.openRead(); } catch (FileNotFoundException e) { } boolean success = false; mUidStates.clear(); try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, StandardCharsets.UTF_8.name()); int type; int outerDepth = parser.getDepth(); while ((type = parser.next()) ! = XmlPullParser.END_DOCUMENT && (type ! = XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if (tagName.equals("pkg")) { readPackage(parser); } else if (tagName.equals("uid")) { readUidOps(parser); } else { XmlUtils.skipCurrentTag(parser); } } success = true; . }Copy the code

After reading, the tag will be updated randomly when the user manipulates permissions, just look at targetSdkVersion<23,

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { final int uid = mPackageInfo.applicationInfo.uid; for (Permission permission : mPermissions.values()) { if (filterPermissions ! = null && ! ArrayUtils.contains(filterPermissions, permission.getName())) { continue; } <! - 1 if support key, namely targetSdkVersion > 23 that walk 6.0 dynamic permissions management that a - > if (mAppSupportsRuntimePermissions) {... } else { if (! permission.isGranted()) { continue; } int killUid = -1; int mask = 0; if (permission.hasAppOp()) { if (! permission.isAppOpAllowed()) { permission.setAppOpAllowed(true); <! -- Keypoint 3 set appopsManager.mode_allowed --> mAppOps. SetUidMode (permission. GetAppOp (), uid, appopsManager.mode_allowed); killUid = uid; } } if (mask ! = 0) { mPackageManager.updatePermissionFlags(permission.getName(), mPackageInfo.packageName, mask, 0, mUserHandle); } } } return true; }Copy the code

For authorization scenarios, the key is mAppOps. SetUidMode (permission-.getappop (), uid, appopsManager.mode_allowed). This function updates the markup for permissions in AppOpsService and persists information about whether permissions are granted to appops. XML and packes.xml. Specific did not delve into, interested in their own analysis.

@Override public void setUidMode(int code, int uid, int mode) { if (Binder.getCallingPid() ! = Process.myPid()) { mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid(), null); } verifyIncomingOp(code); code = AppOpsManager.opToSwitch(code); synchronized (this) { final int defaultMode = AppOpsManager.opToDefaultMode(code); <! --> UidState UidState = getUidStateLocked(uid, false); if (uidState == null) { if (mode == defaultMode) { return; } uidState = new UidState(uid); uidState.opModes = new SparseIntArray(); uidState.opModes.put(code, mode); mUidStates.put(uid, uidState); scheduleWriteLocked(); } else if (uidState.opModes == null) { if (mode ! = defaultMode) { uidState.opModes = new SparseIntArray(); uidState.opModes.put(code, mode); scheduleWriteLocked(); } } else { if (uidState.opModes.get(code) == mode) { return; } if (mode == defaultMode) { uidState.opModes.delete(code); if (uidState.opModes.size() <= 0) { uidState.opModes = null; } } else { uidState.opModes.put(code, mode); } <! -- Persist to appops --> scheduleWriteLocked(); }}... }Copy the code

One thing to note here: scheduleWriteLocked does not perform writes immediately, but rather lags behind update memory, typically 30 minutes

static final long WRITE_DELAY = DEBUG ? 1000:30 * 60 * 1000;Copy the code

If you delete appops. XML and restart it unexpectedly, such as adb reboot bootloader, all AppOpsService permissions will be cleared and verified as expected. In the case of targetSdkVersion<23, Android6.0 or above, its permission operation is persisted in appops. XML, generally when shutdown, will be persisted once, if not enough time to persist, abnormal shutdown, will be lost. This is similar to run-time permission, abnormal shutdown can also be lost.

How do I check permissions for machines with SDK>=23 when targetSdkVersion>=23

TargetSdkVersion >=23 Check the checkPermission of PermisionChecker. AppOpsService (targetSdkVersion>=23, SDK_Version>=23, AppOpsService (targetSdkVersion>=23)) Permission is not granted and revoked in pairs, as follows:

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { final int uid = mPackageInfo.applicationInfo.uid; for (Permission permission : mPermissions.values()) { if (mAppSupportsRuntimePermissions) { <! -- Keypoint 1 updates both runtim-permission and Appops--> if (permission.hasappop () &&! permission.isAppOpAllowed()) { permission.setAppOpAllowed(true); mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED); } if (! permission.isGranted()) { permission.setGranted(true); mPackageManager.grantRuntimePermission(mPackageInfo.packageName, permission.getName(), mUserHandle); } } else { if (! permission.isGranted()) { continue; } int killUid = -1; int mask = 0; <! Update Appops--> if (permission.hasappop ()) {if (! permission.isAppOpAllowed()) { permission.setAppOpAllowed(true); // Enable the app op. mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED); killUid = uid; }... } } return true; }Copy the code

For 6.0 systems, appops. XML will be updated when authorization is granted regardless of whether targetSdkVersion >=23.

public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { final int uid = mPackageInfo.applicationInfo.uid; for (Permission permission : mPermissions.values()) { ... if (mAppSupportsRuntimePermissions) { if (permission.isSystemFixed()) { return false; } // Revoke the permission if needed. if (permission.isGranted()) { permission.setGranted(false); mPackageManager.revokeRuntimePermission(mPackageInfo.packageName, permission.getName(), mUserHandle); } <! } else {// Legacy apps cannot have a non-granted permission but just in  case. if (! permission.isGranted()) { continue; } int mask = 0; int flags = 0; int killUid = -1; if (permission.hasAppOp()) { if (permission.isAppOpAllowed()) { <! MAppOps. SetUidMode (permission. GetAppOp (), uid, AppOpsManager.MODE_IGNORED); killUid = uid; }... } } return true; }Copy the code

If targetSdkVersion>=23, appops. XML will not be updated when authorization is cancelled. Only when targetSdkVersion<23 will authorization be revoked to key 2. Do not use AppOpsManager for targetSdkVersion>=23.

How to check the permissions of mobile phones below 6.0

For phones below Android6.0, targetVersion is not a concern. First, let me tell you the results of my own verification: it is almost impossible to detect, at the same time, it does not need to detect, and even if detected, it does not have much significance, because the trigger time is when the actual service is called. For domestic ROMs before 4.3 to 6.0, although the use of AppopsManagerService, but not according to The Google model for all permissions adaptation, in this model, also adapted two permissions,

  • Public static final int OP_POST_NOTIFICATION = 11;
  • Public static final int OP_SYSTEM_ALERT_WINDOW = 24;

Google APPOpsService basically hides the entire authentication logic. Through the source code of CM, we can get a look at this part of the code. If the whole permission is adopted 4.3 permission management model, when a permission is denied, this operation will be persisted in appops. This is not the case. This mechanism only works for the following two permissions:

<pkg n="com.xxx"> <uid n="10988"> <! - key point 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" /> <! - point 2 - > < op n = "11" m = "1" t = "1510889291834" r = "1517279030708" / > < op n = "14" t = "1517293452801" / > <! - key point 3 - > < op n = "24" m = "1" / > < op n = "40" t = "1513599239364" d = "600011" / >Copy the code

In a native ROM, if you refuse to grant location permissions, according to the AppOpsService model, that operation should be persisted to appops. XML. However, this is not the case. Where the Persistent Android SYSTEM API is not accessible, only its own ROM is visible. When appops. XML was actually used in Android6.0, there were two sets of permissions management in Android6.0, which was confusing. I don’t know what Google thought, but 6.0 also had a bug: permissions were not granted and permissions were revoked.

The problem with this is that between Android4.3 and Android6.0, there is no same API to detect whether certain permissions have been obtained, because your dynamically updated permissions are not persisted in appops. For roms prior to Android6.0, although not able to detect, you can use the service directly without crashing, because if you really need authentication, its authentication time is actually at the time of service use. Prior to 6.0, AppopsManager could only be used to detect notifications and possibly hover Windows.

Solution for checking permissions (excluding notification permissions)

public boolean selfPermissionGranted(Context context, String permission) {

	boolean ret = true;
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
		if (targetSdkVersion >= Build.VERSION_CODES.M) {
			ret = context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
		} else {
	      ret = PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
		}
         	}else{
              return true;
          }
	return ret;
}	    
Copy the code

Or use all of PermissionChecker’s checkSelfPermission:

public boolean selfPermissionGranted(Context context, String permission) {
   return  PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
}	
Copy the code

conclusion

Android6.0 supports two kinds of dynamic management, run-time permission and emasculated AppOpsService. When targetSdkVersion>23, rumtime-permission is used. When targetSdkVersion<23, you can still dynamically apply for 6.0 privileges if you want to compileSdkVersion after targetSdkVersion<23. However, it is recommended to upgrade targetSdkVersion, which is the right thing to do. For Android6.0 or below, there is no system check method for other permissions except notification (and possibly floating window). Whether Context checkPermission or AppopsManager checkOp, it is basically valid after Android6.0.

Android permission check API checkSelfPermission issue only for reference, welcome to correct