Store not-yet-restored prefs for multiple users

This fixes the issue where restored personal notification
preferences could be lost if a managed profile restore was triggered
before the personal packages finished downloading.

Test: atest
Fixes: 142801019
Change-Id: Ic882c806e4f537dffcdca2f6ef1cdd98b0fdb3c7
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 887dbb3..0a3c581 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -22,6 +22,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
@@ -125,7 +126,7 @@
 
     // pkg|uid => PackagePreferences
     private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
-    // pkg => PackagePreferences
+    // pkg|userId => PackagePreferences
     private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
 
     private final Context mContext;
@@ -172,9 +173,6 @@
         String tag = parser.getName();
         if (!TAG_RANKING.equals(tag)) return;
         synchronized (mPackagePreferences) {
-            // Clobber groups and channels with the xml, but don't delete other data that wasn't
-            // present at the time of serialization.
-            mRestoredWithoutUids.clear();
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 tag = parser.getName();
                 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
@@ -200,7 +198,8 @@
                             }
                             boolean skipWarningLogged = false;
 
-                            PackagePreferences r = getOrCreatePackagePreferencesLocked(name, uid,
+                            PackagePreferences r = getOrCreatePackagePreferencesLocked(
+                                    name, userId, uid,
                                     XmlUtils.readIntAttribute(
                                             parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
                                     XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
@@ -311,17 +310,27 @@
         return mPackagePreferences.get(key);
     }
 
-    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid) {
-        return getOrCreatePackagePreferencesLocked(pkg, uid,
+    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
+            int uid) {
+        return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
                 DEFAULT_ALLOW_BUBBLE);
     }
 
-    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid,
-            int importance, int priority, int visibility, boolean showBadge, boolean allowBubble) {
+    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
+            @UserIdInt int userId, int uid) {
+        return getOrCreatePackagePreferencesLocked(pkg, userId, uid,
+                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
+                DEFAULT_ALLOW_BUBBLE);
+    }
+
+    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
+            @UserIdInt int userId, int uid, int importance, int priority, int visibility,
+            boolean showBadge, boolean allowBubble) {
         final String key = packagePreferencesKey(pkg, uid);
         PackagePreferences
-                r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
+                r = (uid == UNKNOWN_UID)
+                ? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
                 : mPackagePreferences.get(key);
         if (r == null) {
             r = new PackagePreferences();
@@ -340,7 +349,7 @@
             }
 
             if (r.uid == UNKNOWN_UID) {
-                mRestoredWithoutUids.put(pkg, r);
+                mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
             } else {
                 mPackagePreferences.put(key, r);
             }
@@ -382,6 +391,10 @@
 
     private boolean createDefaultChannelIfNeededLocked(PackagePreferences r) throws
             PackageManager.NameNotFoundException {
+        if (r.uid == UNKNOWN_UID) {
+            return false;
+        }
+
         if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
             r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
                     com.android.internal.R.string.default_notification_channel_label));
@@ -1769,17 +1782,18 @@
                 synchronized (mPackagePreferences) {
                     mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
                 }
-                mRestoredWithoutUids.remove(pkg);
+                mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
                 updated = true;
             }
         } else {
             for (String pkg : pkgList) {
                 // Package install
-                final PackagePreferences r = mRestoredWithoutUids.get(pkg);
+                final PackagePreferences r =
+                        mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId));
                 if (r != null) {
                     try {
                         r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
-                        mRestoredWithoutUids.remove(pkg);
+                        mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
                         synchronized (mPackagePreferences) {
                             mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
                         }
@@ -1910,6 +1924,10 @@
         return pkg + "|" + uid;
     }
 
+    private static String unrestoredPackageKey(String pkg, @UserIdInt int userId) {
+        return pkg + "|" + userId;
+    }
+
     private static class PackagePreferences {
         String pkg;
         int uid = UNKNOWN_UID;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index a1322b9..776c00e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2715,4 +2715,57 @@
         assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel, true));
         assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel1, true));
     }
+
+    @Test
+    public void testRestoreMultiUser() throws Exception {
+        String pkg = "restore_pkg";
+        String channelId = "channelId";
+        int user0Importance = 3;
+        int user10Importance = 4;
+        when(mPm.getPackageUidAsUser(eq(pkg), anyInt())).thenReturn(UserHandle.USER_NULL);
+
+        // both users have the same package, but different notification settings
+        final String xmlUser0 = "<ranking version=\"1\">\n"
+                + "<package name=\"" + pkg + "\" >\n"
+                + "<channel id=\"" + channelId + "\" name=\"hi\""
+                + " importance=\"" + user0Importance + "\"/>"
+                + "</package>"
+                + "</ranking>";
+        final String xmlUser10 = "<ranking version=\"1\">\n"
+                + "<package name=\"" + pkg + "\" >\n"
+                + "<channel id=\"" + channelId + "\" name=\"hi\""
+                + " importance=\"" + user10Importance + "\"/>"
+                + "</package>"
+                + "</ranking>";
+
+        // trigger a restore for both users
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xmlUser0.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, true, 0);
+        parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xmlUser10.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, true, 10);
+
+        // "install" package on both users
+        String[] pkgList = new String[] {pkg};
+        int[] uidList0 = new int[] {UserHandle.PER_USER_RANGE};
+        int[] uidList10 = new int[] {UserHandle.PER_USER_RANGE + 1};
+        when(mPm.getPackageUidAsUser(pkg, 0)).thenReturn(uidList0[0]);
+        when(mPm.getPackageUidAsUser(pkg, 10)).thenReturn(uidList10[0]);
+        ApplicationInfo info = new ApplicationInfo();
+        info.targetSdkVersion = Build.VERSION_CODES.Q;
+        when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(info);
+
+        mHelper.onPackagesChanged(false, 0, pkgList, uidList0);
+        mHelper.onPackagesChanged(false, 10, pkgList, uidList10);
+
+        assertEquals(user0Importance,
+                mHelper.getNotificationChannel(pkg, uidList0[0], channelId, false).getImportance());
+        assertEquals(user10Importance, mHelper.getNotificationChannel(
+                pkg, uidList10[0], channelId, false).getImportance());
+    }
 }