Call roles granting only when packages changed

This computes and stores a hash of significant (for PermissionController)
packages state for the time when granting last ran.

Test: - enable DEBUG flag
- using logcat ensure roles granted on first bootloader
- adb reboot
- ensure roles granting skipped
- disable a package
- adb reboot
- ensure roles granting ran on boot

Change-Id: Idaea40c0ea34feaedfbe357627201f85e66876d5
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index 9c43f4d..f218d3a 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -31,6 +31,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import libcore.io.IoUtils;
@@ -63,6 +64,7 @@
     private static final String TAG_HOLDER = "holder";
     private static final String ATTRIBUTE_VERSION = "version";
     private static final String ATTRIBUTE_NAME = "name";
+    private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
 
     @UserIdInt
     private final int mUserId;
@@ -70,11 +72,15 @@
     @GuardedBy("RoleManagerService.mLock")
     private int mVersion;
 
+    @GuardedBy("RoleManagerService.mLock")
+    private String mLastGrantPackagesHash = null;
+
     /**
      * Maps role names to its holders' package names. The values should never be null.
      */
     @GuardedBy("RoleManagerService.mLock")
-    private ArrayMap<String, ArraySet<String>> mRoles;
+    @Nullable
+    private ArrayMap<String, ArraySet<String>> mRoles = null;
 
     @GuardedBy("RoleManagerService.mLock")
     private boolean mDestroyed;
@@ -110,6 +116,23 @@
     }
 
     /**
+     * Get the hash representing the state of packages during the last time initial grants was run
+     */
+    @GuardedBy("RoleManagerService.mLock")
+    public String getLastGrantPackagesHashLocked() {
+        return mLastGrantPackagesHash;
+    }
+
+    /**
+     * Set the hash representing the state of packages during the last time initial grants was run
+     */
+    @GuardedBy("RoleManagerService.mLock")
+    public void setLastGrantPackagesHashLocked(String lastGrantPackagesHash) {
+        mLastGrantPackagesHash = lastGrantPackagesHash;
+        writeAsyncLocked();
+    }
+
+    /**
      * Get whether the role is available.
      *
      * @param roleName the name of the role to get the holders for
@@ -227,11 +250,11 @@
      * Schedule writing the state to file.
      */
     @GuardedBy("RoleManagerService.mLock")
-    private void writeAsyncLocked() {
+    void writeAsyncLocked() {
         throwIfDestroyedLocked();
         int version = mVersion;
         ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
-        for (int i = 0, size = mRoles.size(); i < size; ++i) {
+        for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
             String roleName = mRoles.keyAt(i);
             ArraySet<String> roleHolders = mRoles.valueAt(i);
             roleHolders = new ArraySet<>(roleHolders);
@@ -240,11 +263,12 @@
         mWriteHandler.removeCallbacksAndMessages(null);
         // TODO: Throttle writes.
         mWriteHandler.sendMessage(PooledLambda.obtainMessage(
-                RoleUserState::writeSync, this, version, roles));
+                RoleUserState::writeSync, this, version, roles, mLastGrantPackagesHash));
     }
 
     @WorkerThread
-    private void writeSync(int version, @NonNull ArrayMap<String, ArraySet<String>> roles) {
+    private void writeSync(int version, @NonNull ArrayMap<String, ArraySet<String>> roles,
+            String packagesHash) {
         AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
         FileOutputStream out = null;
         try {
@@ -256,7 +280,7 @@
                     "http://xmlpull.org/v1/doc/features.html#indent-output", true);
             serializer.startDocument(null, true);
 
-            serializeRoles(serializer, version, roles);
+            serializeRoles(serializer, version, roles, packagesHash);
 
             serializer.endDocument();
             atomicFile.finishWrite(out);
@@ -272,9 +296,11 @@
 
     @WorkerThread
     private void serializeRoles(@NonNull XmlSerializer serializer, int version,
-            @NonNull ArrayMap<String, ArraySet<String>> roles) throws IOException {
+            @NonNull ArrayMap<String, ArraySet<String>> roles, String packagesHash)
+            throws IOException {
         serializer.startTag(null, TAG_ROLES);
         serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
+        serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
         for (int i = 0, size = roles.size(); i < size; ++i) {
             String roleName = roles.keyAt(i);
             ArraySet<String> roleHolders = roles.valueAt(i);
@@ -341,6 +367,7 @@
     private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException,
             XmlPullParserException {
         mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
+        mLastGrantPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
         mRoles = new ArrayMap<>();
 
         int type;