Android Dynamic Rights Management Model (4.3-6.0)

Posted by umrguy on Fri, 05 Jul 2019 18:40:26 +0200

Google has tried to introduce the AppOpsManager dynamic rights management model since 4.3, but since the technology feels premature, this feature has been hidden in the Release version, so the official Rom has never had a dynamic rights management mechanism.Until Android 6.0, in order to simplify the installation process and facilitate user control, Google officially introduced the runtime permission mechanism, so Android has a mature dynamic rights management mechanism.As we can see, before MarshMallow, all permissions were granted at installation time, and after 6.0, users were allowed to control permissions dynamically at run time.

However, domestic mobile phone manufacturers are different. Although before 6.0, the official version of Google did not have dynamic rights management, domestic mobile phone manufacturers used hidden rights management of Google. If you do not know the principles of rights management, you can not be completely reassured when you make rights adaption to 6.0 in the development process.Therefore, this article mainly covers the following parts:

  • Dynamic Rights Management Model and Principle before Android 6.0--AppOpsManager

  • Dynamic Rights Management Principles for Android 6.0 and Beyond--runtime permission

  • Characteristics and differences of the two types of privileges

Dynamic Rights Management Model before Android 6.0 (official preview) -- AppOpsManager (4.3 source)

AppOpsManager is a dynamic privilege management method introduced by Google in Android 4.3. However, Google feels immature and will always block out this feature at every release.This function is similar to the dynamic rights management performance of the domestic ROM, which is analyzed with the source code of CyanogenMod12. (The source code of the domestic ROM is not available, but the implementation should be similar from the performance point of view).The essence of dynamic management implemented by AppOpsManager is to place authentication within each service, for example, if App wants to apply for location rights, the Location Service Location Manager Service will query AppOpsService for whether the current App location rights are granted, and if authorization is required, a system dialog box will pop up to let users operate andThe result is persisted in the file according to the user's operation. If the user actively updates the corresponding permissions in Setting, the permissions will also be updated and persisted to the file/data/system/appops.xml. The service will be able to selectively authenticate the permissions the next time it requests a service. See the following analysis:

Lift a chestnut: Location Service Location Manager Service: CM12 Source (4.3)

When App uses location services, it usually gets location through Location Manager's requestLocation Updates. App actually requests Location Manager Services to locate through Binder and sends the results back to APP. This is not the focus of this article, but it is a lot of analysis.First, look at the common ways to locate services:

public void requestLocation() {
<!--Key point 1-->
  LocationManager  locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
    try {
        if (locationManager != null) {
<!--Key point 2-->
            locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 0, mLocationListener);
        }
    } catch (Exception e) {
    }
}

Key point 1 actually uses ServiceManager's getService to obtain LocationManagerService's agent. If successful, enter Key point 2 to request LocationManagerService to locate through requestLocationUpdates. Location results are passed to APP through Binder communication, and APP uses listener to obtain location information.Omit the intermediate process and go directly to LocationManagerService.java

@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
        PendingIntent intent, String packageName) {
    if (request == null) request = DEFAULT_LOCATION_REQUEST;
    checkPackageName(packageName);
    <!--Key Function 1 Query Manifest File, whether the permission to locate is declared, and the accuracy level of the location -->
    int allowedResolutionLevel = getCallerAllowedResolutionLevel();
    checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
            request.getProvider());
    . . . 
    <!--Get Call app Of pid with uid-->
    final int pid = Binder.getCallingPid();
    final int uid = Binder.getCallingUid();
    long identity = Binder.clearCallingIdentity();
    try {
    <!--Key function 2 checks whether permissions are dynamically authorized or denied-->
        checkLocationAccess(uid, packageName, allowedResolutionLevel);
        ...
    } finally {
        Binder.restoreCallingIdentity(identity);
    }
}

First, focus on the ILocationListener parameter in the requestLocationUpdates function, which is actually a Binder object used to locate the return of information.Looking at key point 1, the authentication mechanism for Android 4.3 first queries whether the corresponding permissions are declared in Manifest. This is the first step. getCallerAllowedResolutionLevel queries whether APP s are declared in Manifest by calling getAllowedResolutionLevel and obtains positioning accuracy, checkResolutionLevel IsSufficientForProviderUses is to see if this precision is supported, not to go into it.

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

Dynamic authentication occurs at key point 2. CheckLocationAccess is the entry point for dynamic authentication of location services. In the checkLocationAccess function, authentication requests are sent to the AppOpsService service. AppOpsService knows through checkOp whether the current APP needs authorization and whether it has been authorized:

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

Key point 1 is the entry to invoke AppOpsServiceAuthentication. mAppOps is the AppOpsServiceService proxy that LocationManagerService acquired at the time of instantiation, or essentially sends requests to AppOpsServicevia Binder.

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

When AppOpsService receives the request, it will authenticate and update the permissions. In the domestic ROM, it often encounters an authorization page with a countdown. Users can choose to allow, reject and prompt, which corresponds to several ways of handling AppOpsService.

@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);
          ...
          <!--Key point 1-->
        if (switchOp.mode == AppOpsManager.MODE_IGNORED ||
            switchOp.mode == AppOpsManager.MODE_ERRORED) {
            op.rejectTime = System.currentTimeMillis();
            op.ignoredCount++;
            return switchOp.mode;
           <!--Key point 2-->
        } else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) {
            op.time = System.currentTimeMillis();
            op.rejectTime = 0;
            op.allowedCount++;
            return AppOpsManager.MODE_ALLOWED;
        } else {
            op.noteOpCount++;
            <!--Key function 3-->
            userDialogResult = askOperationLocked(code, uid, packageName,
                switchOp);
        }
    }
      <!--Key function 4-->
    return userDialogResult.get();
}

Key points 1 and 2 refer to scenarios that have already been operated on. If authorized, return to authorized success directly. If denied, return to authorized failure directly. And 3 is our common authorization entry dialog box: askOperationLocked displays a system dialog box waiting for the user to select when clicking Allow orAfter rejection, AppOpsServie logs operations and notifies Server whether to continue providing services or reject them.Key point 4 involves a synchronization problem. In a home ROM, the thread requesting permission is blocked (even the UI thread), because the authenticated Binder communication is synchronized, and the server waits until the user operates to return the result to the client, which causes the client requesting thread to be blocked.Until the end of the user operation.AskOperationLocked sends authentication messages through mHandler and returns a PermissionDialogResult.Result that supports blocking operations, blocking through its get function and waiting for the operation to finish, to see the specific processing

public AppOpsService(File storagePath) {
    mStrictEnable = AppOpsManager.isStrictEnable();
    mFile = new AtomicFile(storagePath);
    mLooper = Looper.myLooper();
    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");
                         <!--Key point 1-->
                        Dialog d = new PermissionDialog(mContext,
                            AppOpsService.this, code, uid,
                            packageName);
                        op.dialogResult.mDialog = (PermissionDialog)d;
                        d.show();
                    }
                }
            }break;
            }
        }
    };
    readWhitelist();
    readState();
}

Key point 1: A new system PermissionDialog is created and displayed, while the get() function of PermissionDialogResult.Result above keeps the server-side Binder thread blocked, which timeout is less than the time the system takes to set the ANR, so don't worry about ANR until the AppOpsService thread has finished operating and notify Bind through notifyAll The Er thread operation ends before the result is returned to the APP side, waking up the blocked waiting APP. The simple principle is as follows

   class PermissionDialogResult {
        public final static class Result {
            <!--Key point 1 wake up-->
            public void set(int res) {
                synchronized (this) {
                    mHasResult = true;
                    mResult = res;
                    notifyAll();
                } }
            <!--Key point 2 Binder Thread Blocked Wait-->
             public int get() {
                synchronized (this) {
                    while (!mHasResult) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                        }  } }
                return mResult; }
            boolean mHasResult = false;
            int mResult;
        }
        <!--Key point 3 Other threads wake up Binder Thread's entry-->
        public void notifyAll(int mode) {
            synchronized(this) {
                while(resultList.size() != 0) {
                    Result res = resultList.get(0);
                    res.set(mode);
                    resultList.remove(0);
                }  }
        }
    }

The disadvantage of this dynamic rights management model is that before you actually use a service, you don't know if you have permissions, you need to request a service first, and the corresponding service applies for authentication to AppOpsService. In other words, the permissions are maintained by the service + AppOpsService, which is not flexible enough. It may also be that Google hasn't always had permissions.Reason for release, this immature set of rights management will be abandoned until Android 6.0 runtim-permmission is released.The general flow is shown below.

Pre-6.0 Android distribution source support for dynamic rights management was almost zero

Between Android 4.3 and 5.1, although App can get instances of AppOpsManager, the interface setMode s that have true dynamic operation privileges are hidden, as shown below with the property hide:

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

Traversal source code also only NotificationManagerService This system application uses setMode, which means the distribution version, only notifications support dynamic management.

public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
    checkCallerIsSystem();
    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));
    }
}

Android 6.0 Rights Management Principles

Starting with Android 6.0, the runtime-permission mechanism is natively supported, users can authorize/deauthorize at any time, and APP knows whether they have the required permissions before requesting services. As a result, the APP side can control applications for permissions independently and more flexibly.First, look at the permission query to see if you have already acquired a permission: PermissionChecker, a tool class, is provided in the support-v4 compatibility package to check permission acquisition.

public static int checkPermission(@NonNull Context context, @NonNull String permission,
        int pid, int uid, String packageName) {
    <!--Key point 1 -->
    if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
        return PERMISSION_DENIED;
    }
    ...
    return PERMISSION_GRANTED;
}

Here we are only concerned with the more important key point, context.checkPermission, which ultimately sends requests to the Activity Manager Service through the Activity Manager Native.

/** @hide */
@Override
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
    try {
        return ActivityManagerNative.getDefault().checkPermissionWithToken(
                permission, pid, uid, callerToken);
    } catch (RemoteException e) {
        return PackageManager.PERMISSION_DENIED;
    }
}

The corresponding processing on the ActivityManagerService side is


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

Then call ActivityManager.checkComponentPermission, AppGlobals.getPackageManager().checkUidPermission(permission, uid);

/** @hide */
public static int checkComponentPermission(String permission, int uid,
        int owningUid, boolean exported) {
    
    <!--root and System Process can get all permissions-->
    if (uid == 0 || uid == Process.SYSTEM_UID) {
        return PackageManager.PERMISSION_GRANTED;
    }
        . . . 
    <!--Permission Queries for Common Applications-->
    try {
        return AppGlobals.getPackageManager()
                .checkUidPermission(permission, uid);
    } catch (RemoteException e) {
    }
    return PackageManager.PERMISSION_DENIED;
}

Finally, we call PackageManagerService.java to see if there is any permission. From here, we can know that the query of permissions is actually done through PKMS, and then we will see the update, persistence and recovery of permissions through PKMS.The permission query function checkUidPermission is supported in different versions, but the implementation of Android 6.0 is very different from previous versions. Take a look at Android 5.0's checkUidPermission first: Obtain the permission list of current APP mainly through Setting. For pre-6.0 APPs, these permissions are all static applications.Or, as long as it is stated in the enifest file, it is considered an application.

 public int checkUidPermission(String permName, int uid) {
        final boolean enforcedDefault = isPermissionEnforcedDefault(permName);
        synchronized (mPackages) {
        <!--PackageManagerService.Setting.mUserIds In an array, based on uid lookup uid(that is package)Permission List-->
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
            <!--Key point 1 -->
                GrantedPermissions gp = (GrantedPermissions)obj;
                if (gp.grantedPermissions.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } 
            ...
        return PackageManager.PERMISSION_DENIED;
    }

GrantedPermissions is a collection of permissions corresponding to an APP with a list of permissions HashSet <String> grantedPermissions = new HashSet <String>(), which contains the corresponding string whenever permissions are applied in Menifest and is completely static.But 6.0 runtime-permmison is different, take a look at Android 6.0+checkUidPermission

 @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();
                if (permissionsState.hasPermission(permName, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } 
            ...
        }
        return PackageManager.PERMISSION_DENIED;
    }
    

After Android 6.0, the APP permission status corresponds to the PermissionsState object, and it is not sufficient to declare in Menifest alone to determine whether or not you have some permission:


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

From the code above, it is clear that after 6.0, in addition to declaring permissions, it must be authorized. The process of judging is roughly as follows. Next, take a look at the application for dynamic permissions:

Dynamic application for permissions

From the permissions query above, you can know if you have permissions or if you don't need to apply, Android 6.0 can dynamically apply for permissions through ActivityCompat in the V4 package, which is already compatible with different versions:


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

If the system is below 6.0, ActivityCompat queries directly through PKMS whether permissions have been applied for in Manifest or not, if so, by default, and passes the results back to Activity or Fragment through onRequestPermissionsResult.For 6.0+, go to the next branch and call activity.requestPermissions to apply for permissions.

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

Here Intent is actually an Intent acquired through PackageManager (the ApplicationPackageManager implementation class)


    public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
    Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
    intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
    intent.setPackage(getPermissionControllerPackageName());
    return intent;
}

The above function is mainly used to obtain suspended authorization Activity component information: in fact, it is GrantPermissionsActivity, which is an Activity within the PackageInstaller system application, the details are not in-depth, you can query.All in all, you get the GrantPermissionsActivity of the PackageInstaller and start it.PackageInstaller is responsible for the installation and uninstallation of the application. It also contains some logic for authorization management. A simple look at the Grant PermissionsActivity style is similar to a dialog box:

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

This is a dialog-like floating window style 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>

The GrantPermissionsActivity starts with a dynamic update permission process, which is different from AppOpsService introduced in 4.3. Permission requests for 6.0 must be asynchronous and will not block the request thread because it follows the startActivityForResult process and the Activity declaration cycle.

Update permissions dynamically

Through the above process, we entered GrantPermissionsActivity to update the permission information in PKMS according to the user's operation. As to why we should communicate with PKMS, because PKMS is the maintainer of permission information, the management of permissions in memory and the persistence of permissions are the responsibility of PKMS, you will see that PKMS will grant permissions later.Persist to runtime-permissions.xml.Of course, if permissions have already been granted, there is no need to go into GrantPermissionsActivity again.Take a look directly at authorization operations:

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
    final int uid = mPackageInfo.applicationInfo.uid;
    for (Permission permission : mPermissions.values()) {
            ...
            <!--To grant authorization-->
            // Grant the permission if needed.
            if (!permission.isGranted()) {
                permission.setGranted(true);
                mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                        permission.getName(), mUserHandle);
            }
            
    }
    

As you can see, the final step is to call PackageManager to update App's runtime permissions and walk into the PackageManager Service.

 @Override
    public void grantRuntimePermission(String packageName, String name, final int userId) {
  
            <!--Is Key Point 1 query in Menifest Declared in-->
              enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
            sb = (SettingBase) pkg.mExtras;
            final PermissionsState permissionsState = sb.getPermissionsState();
              ...
             <!--Key Point 2 Authorization-->
            final int result = permissionsState.grantRuntimePermission(bp, userId);
            ...
            <!--Key Point 3 Persistence-->    
            // Not critical if that is lost - app has to request again.
            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
        }
        

Key point 1: The enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission function is designed to determine that the sensitive permissions for the application have been declared in Menifest, otherwise an exception will be thrown directly causing the crash.Key point 2 is the authorization operation, which is to update the in-memory permission information for App applications. Last key point 3 is to persist permissions to local files, so that after the phone restarts, the previously saved permissions can not be lost. First, look at PermissionsState's operation on permission information in memory:

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

<!--Dynamically add update memory Permison -->

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

The final step is to update the information to the Setting object, and the next step is to persist the updated permissions to the file to mSettings.writeRuntimePermissionsForUserLPr.

Persistence of Runtime-Permission privileges

mSettings.writeRuntimePermissionsForUserLPr persists updated permissions to local files.

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

Where does persistence go?

 private void writePermissionsSync(int userId) {
    AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId));
        ...
         FileOutputStream out = null;
            try {
                out = destination.startWrite();
              ...
              }
 }

  private File getUserRuntimePermissionsFile(int userId) {
    File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
    return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
}
  

The value of getUserRuntime PermissionsFile is the directory data/system/0/runtime-permissions.xml, in which runtime permissions are stored. This file is only available on Android 6.0 and has the following contents: Apply package name + permission name + authorization status

  <pkg name="com.snail.xxx">
    <item name="android.permission.CALL_PHONE" granted="true" flags="0" />
    <item name="android.permission.CAMERA" granted="false" flags="1" />
  </pkg>

Runtime-Permission Recovery

Since there is persistence, there must be recovery. The persistent data will be read by PKMS when the mobile phone restarts.On startup, PKMS scans Apk, updates the information in APK AndroidManifest to memory or/data/system/packages.xml file as required. In terms of rights management, packages.xml mainly contains install permission, which is a less sensitive permission. As long as it is declared in Menifest, it is acquired by default and does not need toTo apply dynamically, packages.xml is updated when the APK is upgraded, installed, or uninstalled, while runtime permissions are stored in data/system/0/runtime-permissions.xml and read at startup as well:

boolean readLPw(@NonNull List<UserInfo> users) {
    FileInputStream str = null;
   ...      
  <!--Key point 1--read package Information, including install Permission information (for Android6.0package.xml)-->
    readPackageLPw(parser); 
         ...
 <!--Key point 2 read runtime permmsion permissions information-->
    for (UserInfo user : users) {
        mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
    }
}

Key point 1 corresponds to static APK information and static permissions, key point 2 corresponds to restored read of dynamic permissions, and all permissions will be placed in the data/system/packages.xml file before Android 6.0.After Android 6.0, permissions are divided into run-time permissions and normal permissions, which are still in data/system/packages.xml, but run-time permissions are in data/system/users/0/runtime-permissions.xml files and support dynamic updates.The general flow is as follows:

What happens when Android 6.0 dynamically applies for common permissions

In Android 6.0, common privileges also support the model of runtime privileges, except that common privileges are already obtained at installation time, grand="true", and do not cancel the entry, so they are always authorized, and when applying for intall privileges, they go directly to the successful branch of the application.If you look at the packages.xml, you'll find that it corresponds to the analysis:

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

Where are the key nodes for Android dynamic management permissions

For imperfect privilege management models prior to Android 6.0, the contact between authentication and application for privileges occurs when requesting system services. AppopsManager is requested to be authenticated uniformly by system services. This point is managed uniformly by AppOpsService services within each system service, but this mode of operation is too systematic intervention.Many, not conducive to APP autonomous control rights.In 6.0, authentication is separated from application. The APP side can query whether it has certain rights first. If it does not apply again, it can avoid the confusion that the service side participates in rights management and be more clear and flexible.

Topics: Android xml Google Mobile