Noti importance from certain Roles cannot be modified

by the notification assistant or user, like the default
phone app.

Fixes: 129358763
Test: atest
Change-Id: I40ef7ff403e2b0d81abe09f15c8804c2d3d2fb8a
diff --git a/api/test-current.txt b/api/test-current.txt
index 6f8f310..8dff8f4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -315,7 +315,9 @@
   }
 
   public final class NotificationChannel implements android.os.Parcelable {
+    method public boolean isImportanceLockedByCriticalDeviceFunction();
     method public boolean isImportanceLockedByOEM();
+    method public void setImportanceLockedByCriticalDeviceFunction(boolean);
     method public void setImportanceLockedByOEM(boolean);
   }
 
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 5cdf85a..69ec831 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -170,6 +170,7 @@
     private boolean mBlockableSystem = false;
     private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
     private boolean mImportanceLockedByOEM;
+    private boolean mImportanceLockedDefaultApp;
 
     /**
      * Creates a notification channel.
@@ -656,11 +657,27 @@
      * @hide
      */
     @TestApi
+    public void setImportanceLockedByCriticalDeviceFunction(boolean locked) {
+        mImportanceLockedDefaultApp = locked;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
     public boolean isImportanceLockedByOEM() {
         return mImportanceLockedByOEM;
     }
 
     /**
+     * @hide
+     */
+    @TestApi
+    public boolean isImportanceLockedByCriticalDeviceFunction() {
+        return mImportanceLockedDefaultApp;
+    }
+
+    /**
      * Returns whether the user has chosen the importance of this channel, either to affirm the
      * initial selection from the app, or changed it to be higher or lower.
      * @see #getImportance()
@@ -834,6 +851,9 @@
             out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble()));
         }
 
+        // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
+        // truth and so aren't written to this xml file
+
         out.endTag(null, TAG_CHANNEL);
     }
 
@@ -942,7 +962,8 @@
         return sb.toString();
     }
 
-    public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR = new Creator<NotificationChannel>() {
+    public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR =
+            new Creator<NotificationChannel>() {
         @Override
         public NotificationChannel createFromParcel(Parcel in) {
             return new NotificationChannel(in);
@@ -983,7 +1004,8 @@
                 && Arrays.equals(mVibration, that.mVibration)
                 && Objects.equals(getGroup(), that.getGroup())
                 && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
-                && mImportanceLockedByOEM == that.mImportanceLockedByOEM;
+                && mImportanceLockedByOEM == that.mImportanceLockedByOEM
+                && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp;
     }
 
     @Override
@@ -993,7 +1015,7 @@
                 getUserLockedFields(),
                 isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                 getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
-                mImportanceLockedByOEM);
+                mImportanceLockedByOEM, mImportanceLockedDefaultApp);
         result = 31 * result + Arrays.hashCode(mVibration);
         return result;
     }
@@ -1022,6 +1044,7 @@
                 + ", mBlockableSystem=" + mBlockableSystem
                 + ", mAllowBubbles=" + mAllowBubbles
                 + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+                + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
                 + '}';
         pw.println(prefix + output);
     }
@@ -1049,6 +1072,7 @@
                 + ", mBlockableSystem=" + mBlockableSystem
                 + ", mAllowBubbles=" + mAllowBubbles
                 + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+                + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
                 + '}';
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9630727c..d287b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -552,6 +552,9 @@
             mEntry.mIsSystemNotification = isSystemNotification(mContext, mStatusBarNotification);
         }
 
+        isNonblockable |= mEntry.channel.isImportanceLockedByOEM();
+        isNonblockable |= mEntry.channel.isImportanceLockedByCriticalDeviceFunction();
+
         if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
             if (mEntry.mIsSystemNotification) {
                 if (mEntry.channel != null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 8c5f6f2..5ea4636 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -212,7 +212,7 @@
      *
      * @return a notification with no special properties
      */
-    private Notification createNotification() {
+    public Notification createNotification() {
         return createNotification(false /* isGroupSummary */, null /* groupKey */);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 0aa103f..8077e3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -37,7 +37,9 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.app.Notification;
 import android.app.NotificationChannel;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -355,4 +357,30 @@
         mGroupRow.setUserExpanded(true);
         Assert.assertTrue(mGroupRow.isExpanded());
     }
+
+    @Test
+    public void testGetIsNonblockable() throws Exception {
+        ExpandableNotificationRow row =
+                mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
+
+        assertFalse(row.getIsNonblockable());
+    }
+
+    @Test
+    public void testGetIsNonblockable_oemLocked() throws Exception {
+        ExpandableNotificationRow row =
+                mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
+        row.getEntry().channel.setImportanceLockedByOEM(true);
+
+        assertTrue(row.getIsNonblockable());
+    }
+
+    @Test
+    public void testGetIsNonblockable_criticalDeviceFunction() throws Exception {
+        ExpandableNotificationRow row =
+                mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
+        row.getEntry().channel.setImportanceLockedByCriticalDeviceFunction(true);
+
+        assertTrue(row.getIsNonblockable());
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 20b8987..6a70d82 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -84,6 +84,7 @@
 
 import android.Manifest;
 import android.Manifest.permission;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -107,6 +108,8 @@
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.backup.BackupManager;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.companion.ICompanionDeviceManager;
@@ -151,6 +154,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.DeviceConfig;
@@ -216,7 +220,6 @@
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
 import com.android.server.notification.ManagedServices.UserProfiles;
 import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PhoneWindowManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -249,6 +252,7 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
 
@@ -300,6 +304,12 @@
             Adjustment.KEY_TEXT_REPLIES,
             Adjustment.KEY_USER_SENTIMENT};
 
+    static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
+            RoleManager.ROLE_DIALER,
+            RoleManager.ROLE_SMS,
+            RoleManager.ROLE_EMERGENCY
+    };
+
     // When #matchesCallFilter is called from the ringer, wait at most
     // 3s to resolve the contacts. This timeout is required since
     // ContactsProvider might take a long time to start up.
@@ -343,6 +353,8 @@
     private IDeviceIdleController mDeviceIdleController;
     private IUriGrantsManager mUgm;
     private UriGrantsManagerInternal mUgmInternal;
+    private RoleObserver mRoleObserver;
+    private UserManager mUm;
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
@@ -553,18 +565,13 @@
         }
     }
 
-    UserManagerService getUserManagerService() {
-        return UserManagerService.getInstance();
-    }
-
     void readPolicyXml(InputStream stream, boolean forRestore, int userId)
             throws XmlPullParserException, NumberFormatException, IOException {
         final XmlPullParser parser = Xml.newPullParser();
         parser.setInput(stream, StandardCharsets.UTF_8.name());
         XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY);
         boolean migratedManagedServices = false;
-        boolean ineligibleForManagedServices = forRestore
-                && getUserManagerService().isManagedProfile(userId);
+        boolean ineligibleForManagedServices = forRestore && mUm.isManagedProfile(userId);
         int outerDepth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
@@ -612,7 +619,8 @@
         mAssistants.resetDefaultAssistantsIfNecessary();
     }
 
-    private void loadPolicyFile() {
+    @VisibleForTesting
+    protected void loadPolicyFile() {
         if (DBG) Slog.d(TAG, "loadPolicyFile");
         synchronized (mPolicyFile) {
             InputStream infile = null;
@@ -1530,7 +1538,8 @@
             NotificationUsageStats usageStats, AtomicFile policyFile,
             ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
             UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
-            IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps) {
+            IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps,
+            UserManager userManager) {
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1552,6 +1561,7 @@
         mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
         mDpm = dpm;
+        mUm = userManager;
 
         mHandler = new WorkerHandler(looper);
         mRankingThread.start();
@@ -1697,14 +1707,16 @@
                         AppGlobals.getPackageManager()),
                 new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
                 null, snoozeHelper, new NotificationUsageStats(getContext()),
-                new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"),
+                new AtomicFile(new File(
+                        systemDir, "notification_policy.xml"), "notification-policy"),
                 (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
                 getGroupHelper(), ActivityManager.getService(),
                 LocalServices.getService(UsageStatsManagerInternal.class),
                 LocalServices.getService(DevicePolicyManagerInternal.class),
                 UriGrantsManager.getService(),
                 LocalServices.getService(UriGrantsManagerInternal.class),
-                (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE));
+                (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
+                getContext().getSystemService(UserManager.class));
 
         // register for various Intents
         IntentFilter filter = new IntentFilter();
@@ -1827,6 +1839,9 @@
             mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
             mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
             mZenModeHelper.onSystemReady();
+            mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
+                    getContext().getMainExecutor());
+            mRoleObserver.init();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             // This observer will force an update when observe is called, causing us to
             // bind to listener services.
@@ -8074,6 +8089,98 @@
         }
     }
 
+    class RoleObserver implements OnRoleHoldersChangedListener {
+        // Role name : user id : list of approved packages
+        private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;
+
+        private final RoleManager mRm;
+        private final Executor mExecutor;
+
+        RoleObserver(@NonNull RoleManager roleManager,
+                @NonNull @CallbackExecutor Executor executor) {
+            mRm = roleManager;
+            mExecutor = executor;
+        }
+
+        public void init() {
+            List<UserInfo> users = mUm.getUsers();
+            mNonBlockableDefaultApps = new ArrayMap<>();
+            for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
+                final ArrayMap<Integer, ArraySet<String>> userToApprovedList = new ArrayMap<>();
+                mNonBlockableDefaultApps.put(NON_BLOCKABLE_DEFAULT_ROLES[i], userToApprovedList);
+                for (int j = 0; j < users.size(); j++) {
+                    Integer userId = users.get(j).getUserHandle().getIdentifier();
+                    ArraySet<String> approvedForUserId = new ArraySet<>(mRm.getRoleHoldersAsUser(
+                            NON_BLOCKABLE_DEFAULT_ROLES[i], UserHandle.of(userId)));
+                    userToApprovedList.put(userId, approvedForUserId);
+                    mPreferencesHelper.updateDefaultApps(userId, null, approvedForUserId);
+                }
+            }
+
+            mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
+        }
+
+        @VisibleForTesting
+        public boolean isApprovedPackageForRoleForUser(String role, String pkg, int userId) {
+            return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
+        }
+
+        /**
+         * Convert the assistant-role holder into settings. The rest of the system uses the
+         * settings.
+         *
+         * @param roleName the name of the role whose holders are changed
+         * @param user the user for this role holder change
+         */
+        @Override
+        public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+            // we only care about a couple of the roles they'll tell us about
+            boolean relevantChange = false;
+            for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
+                if (NON_BLOCKABLE_DEFAULT_ROLES[i].equals(roleName)) {
+                    relevantChange = true;
+                    break;
+                }
+            }
+
+            if (!relevantChange) {
+                return;
+            }
+
+            ArraySet<String> roleHolders = new ArraySet<>(mRm.getRoleHoldersAsUser(roleName, user));
+
+            // find the diff
+            ArrayMap<Integer, ArraySet<String>> prevApprovedForRole =
+                    mNonBlockableDefaultApps.getOrDefault(roleName, new ArrayMap<>());
+            ArraySet<String> previouslyApproved =
+                    prevApprovedForRole.getOrDefault(user.getIdentifier(), new ArraySet<>());
+
+            ArraySet<String> toRemove = new ArraySet<>();
+            ArraySet<String> toAdd = new ArraySet<>();
+
+            for (String previous : previouslyApproved) {
+                if (!roleHolders.contains(previous)) {
+                    toRemove.add(previous);
+                }
+            }
+            for (String nowApproved : roleHolders) {
+                if (!previouslyApproved.contains(nowApproved)) {
+                    toAdd.add(nowApproved);
+                }
+            }
+
+            // store newly approved apps
+            prevApprovedForRole.put(user.getIdentifier(), roleHolders);
+            mNonBlockableDefaultApps.put(roleName, prevApprovedForRole);
+
+            // update what apps can be blocked
+            mPreferencesHelper.updateDefaultApps(user.getIdentifier(), toRemove, toAdd);
+
+            // RoleManager is the source of truth for this data so we don't need to trigger a
+            // write of the notification policy xml for this change
+        }
+    }
+
     public static final class DumpFilter {
         public boolean filtered = false;
         public String pkgFilter;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index de93120..4cc08d8 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -783,7 +783,8 @@
         // Consider Notification Assistant and system overrides to importance. If both, system wins.
         if (!getChannel().hasUserSetImportance()
                 && mAssistantImportance != IMPORTANCE_UNSPECIFIED
-                && !getChannel().isImportanceLockedByOEM()) {
+                && !getChannel().isImportanceLockedByOEM()
+                && !getChannel().isImportanceLockedByCriticalDeviceFunction()) {
             mImportance = mAssistantImportance;
             mImportanceExplanationCode = MetricsEvent.IMPORTANCE_EXPLANATION_ASST;
         }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 660309c..a3e90dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -37,6 +37,8 @@
 import android.service.notification.RankingHelperProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
@@ -100,6 +102,7 @@
     private static final boolean DEFAULT_SHOW_BADGE = true;
     private static final boolean DEFAULT_ALLOW_BUBBLE = true;
     private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
+    private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE  = false;
 
     /**
      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
@@ -659,6 +662,7 @@
                 channel.setImportanceLockedByOEM(true);
             }
         }
+        channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
         if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
             channel.setLockscreenVisibility(
                     NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
@@ -707,6 +711,10 @@
         if (updatedChannel.isImportanceLockedByOEM()) {
             updatedChannel.setImportance(channel.getImportance());
         }
+        updatedChannel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
+        if (updatedChannel.isImportanceLockedByCriticalDeviceFunction()) {
+            updatedChannel.setImportance(channel.getImportance());
+        }
 
         r.channels.put(updatedChannel.getId(), updatedChannel);
 
@@ -844,6 +852,26 @@
         }
     }
 
+    public void updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<String> toAdd) {
+        synchronized (mPackagePreferences) {
+            for (PackagePreferences p : mPackagePreferences.values()) {
+                if (userId == UserHandle.getUserId(p.uid)) {
+                    if (toRemove != null && toRemove.contains(p.pkg)) {
+                        p.defaultAppLockedImportance = false;
+                        for (NotificationChannel channel : p.channels.values()) {
+                            channel.setImportanceLockedByCriticalDeviceFunction(false);
+                        }
+                    } else if (toAdd != null && toAdd.contains(p.pkg)) {
+                        p.defaultAppLockedImportance = true;
+                        for (NotificationChannel channel : p.channels.values()) {
+                            channel.setImportanceLockedByCriticalDeviceFunction(true);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
             int uid, String groupId, boolean includeDeleted) {
         Preconditions.checkNotNull(pkg);
@@ -1729,8 +1757,11 @@
         boolean showBadge = DEFAULT_SHOW_BADGE;
         boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+        // these fields are loaded on boot from a different source of truth and so are not
+        // written to notification policy xml
         boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
         List<String> futureOemLockedChannels = new ArrayList<>();
+        boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
 
         Delegate delegate = null;
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index aa3135f..3ff85c8 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -29,6 +29,7 @@
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a9eb6ec..00e46a1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -101,6 +101,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
@@ -120,9 +121,6 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 
-import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
-
 import com.android.internal.R;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -149,7 +147,6 @@
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -160,6 +157,9 @@
 import java.util.Set;
 import java.util.function.Consumer;
 
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -226,23 +226,21 @@
     @Mock
     AppOpsManager mAppOpsManager;
     @Mock
-    private UserManagerService mUserMangerService;
-    @Mock
     private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
             mNotificationAssistantAccessGrantedCallback;
+    @Mock
+    UserManager mUm;
 
     // Use a Testable subclass so we can simulate calls from the system without failing.
     private static class TestableNotificationManagerService extends NotificationManagerService {
         int countSystemChecks = 0;
         boolean isSystemUid = true;
         int countLogSmartSuggestionsVisible = 0;
-        UserManagerService mUserManagerService;
         @Nullable
         NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
 
-        TestableNotificationManagerService(Context context, UserManagerService userManagerService) {
+        TestableNotificationManagerService(Context context) {
             super(context);
-            mUserManagerService = userManagerService;
         }
 
         @Override
@@ -279,11 +277,6 @@
         }
 
         @Override
-        UserManagerService getUserManagerService() {
-            return mUserManagerService;
-        }
-
-        @Override
         protected void setNotificationAssistantAccessGrantedForUserInternal(
                 ComponentName assistant, int userId, boolean granted) {
             if (mNotificationAssistantAccessGrantedCallback != null) {
@@ -326,7 +319,7 @@
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
 
-        mService = new TestableNotificationManagerService(mContext, mUserMangerService);
+        mService = new TestableNotificationManagerService(mContext);
 
         // Use this testable looper.
         mTestableLooper = TestableLooper.get(this);
@@ -379,7 +372,7 @@
                     mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
                     mGroupHelper, mAm, mAppUsageStats,
                     mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
-                    mAppOpsManager);
+                    mAppOpsManager, mUm);
             mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
@@ -1920,8 +1913,8 @@
     }
 
     @Test
-    public void testHasCompanionDevice_noService() throws Exception {
-        mService = new TestableNotificationManagerService(mContext, mUserMangerService);
+    public void testHasCompanionDevice_noService() {
+        mService = new TestableNotificationManagerService(mContext);
 
         assertFalse(mService.hasCompanionDevice(mListener));
     }
@@ -2623,7 +2616,7 @@
                 + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
                 + "</dnd_apps>"
                 + "</notification-policy>";
-        when(mUserMangerService.isManagedProfile(10)).thenReturn(true);
+        when(mUm.isManagedProfile(10)).thenReturn(true);
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
                 true,
@@ -2647,7 +2640,7 @@
                 + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
                 + "</dnd_apps>"
                 + "</notification-policy>";
-        when(mUserMangerService.isManagedProfile(10)).thenReturn(false);
+        when(mUm.isManagedProfile(10)).thenReturn(false);
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
                 true,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index e375195..ee09c7e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -952,7 +952,31 @@
     }
 
     @Test
-    public void testApplyImportanceAdjustmentsForNonOemLockedChannels() {
+    public void testIgnoreImportanceAdjustmentsForDefaultAppLockedChannels() {
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setImportanceLockedByCriticalDeviceFunction(true);
+
+        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, groupId /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+
+        Bundle bundle = new Bundle();
+        bundle.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW);
+        Adjustment adjustment = new Adjustment(
+                PKG_O, record.getKey(), bundle, "", record.getUserId());
+
+        record.addAdjustment(adjustment);
+        record.applyAdjustments();
+        record.calculateImportance();
+
+        assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+    }
+
+    @Test
+    public void testApplyImportanceAdjustmentsForNonOemDefaultAppLockedChannels() {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
         channel.setImportanceLockedByOEM(false);
 
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 39e47ec..87f10a4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -62,11 +62,9 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Xml;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.UiServiceTestCase;
 
@@ -91,6 +89,9 @@
 import java.util.Objects;
 import java.util.concurrent.ThreadLocalRandom;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PreferencesHelperTest extends UiServiceTestCase {
@@ -2442,4 +2443,153 @@
         assertEquals(IMPORTANCE_HIGH,
                 mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
     }
+
+    @Test
+    public void testUpdateDefaultApps_add_multiUser() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+        // different uids, same package
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+        mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false);
+        mHelper.createNotificationChannel(PKG_O, UserHandle.PER_USER_RANGE + 1, c, true, true);
+
+        ArraySet<String> toAdd = new ArraySet<>();
+        toAdd.add(PKG_O);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+        assertFalse(mHelper.getNotificationChannel(
+                PKG_O, UserHandle.PER_USER_RANGE + 1, c.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+    }
+
+    @Test
+    public void testUpdateDefaultApps_add_onlyGivenPkg() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);
+
+        ArraySet<String> toAdd = new ArraySet<>();
+        toAdd.add(PKG_O);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+    }
+
+    @Test
+    public void testUpdateDefaultApps_remove() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        // different uids, same package
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+        mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false);
+
+        ArraySet<String> toAdd = new ArraySet<>();
+        toAdd.add(PKG_O);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+
+        ArraySet<String> toRemove = new ArraySet<>();
+        toRemove.add(PKG_O);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, null);
+
+        assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+        assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+    }
+
+    @Test
+    public void testUpdateDefaultApps_addAndRemove() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);
+
+        ArraySet<String> toAdd = new ArraySet<>();
+        toAdd.add(PKG_O);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+
+        // now the default is PKG_N_MR1
+        ArraySet<String> toRemove = new ArraySet<>();
+        toRemove.add(PKG_O);
+        toAdd = new ArraySet<>();
+        toAdd.add(PKG_N_MR1);
+        mHelper.updateDefaultApps(USER.getIdentifier(), toRemove, toAdd);
+
+        assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+        assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+    }
+
+    @Test
+    public void testUpdateDefaultApps_appDoesNotExist_noCrash() {
+        ArraySet<String> toAdd = new ArraySet<>();
+        toAdd.add(PKG_O);
+        ArraySet<String> toRemove = new ArraySet<>();
+        toRemove.add(PKG_N_MR1);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, toAdd);
+    }
+
+    @Test
+    public void testUpdateDefaultApps_channelDoesNotExistYet() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+
+        ArraySet<String> toAdd = new ArraySet<>();
+        toAdd.add(PKG_O);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+
+        mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+                .isImportanceLockedByCriticalDeviceFunction());
+    }
+
+    @Test
+    public void testUpdateNotificationChannel_defaultAppLockedImportance() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+        ArraySet<String> toAdd = new ArraySet<>();
+        toAdd.add(PKG_O);
+        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+        NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
+        update.setAllowBubbles(false);
+
+        mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
+
+        assertEquals(IMPORTANCE_HIGH,
+                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+        assertEquals(false,
+                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble());
+
+        mHelper.updateNotificationChannel(PKG_O, UID_O, update, false);
+
+        assertEquals(IMPORTANCE_HIGH,
+                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
new file mode 100644
index 0000000..91d3e5e
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.role.RoleManager.ROLE_DIALER;
+import static android.app.role.RoleManager.ROLE_EMERGENCY;
+import static android.app.role.RoleManager.ROLE_SMS;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.os.Build.VERSION_CODES.P;
+import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.INotificationManager;
+import android.app.ITransientNotification;
+import android.app.IUriGrantsManager;
+import android.app.Notification;
+import android.app.Notification.MessagingStyle.Message;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.app.role.RoleManager;
+import android.app.usage.UsageStatsManagerInternal;
+import android.companion.ICompanionDeviceManager;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.service.notification.Adjustment;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.NotifyingApp;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.TestablePermissions;
+import android.text.Html;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+
+import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.server.LocalServices;
+import com.android.server.UiServiceTestCase;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+import com.android.server.notification.NotificationManagerService.NotificationAssistants;
+import com.android.server.notification.NotificationManagerService.NotificationListeners;
+import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class RoleObserverTest extends UiServiceTestCase {
+    private TestableNotificationManagerService mService;
+    private NotificationManagerService.RoleObserver mRoleObserver;
+
+    private TestableContext mContext = spy(getContext());
+
+    @Mock
+    private PreferencesHelper mPreferencesHelper;
+    @Mock
+    private UserManager mUm;
+    @Mock
+    private Executor mExecutor;
+    @Mock
+    private RoleManager mRoleManager;
+
+    private List<UserInfo> mUsers;
+
+    private static class TestableNotificationManagerService extends NotificationManagerService {
+
+        TestableNotificationManagerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void handleSavePolicyFile() {
+            return;
+        }
+
+        @Override
+        protected void loadPolicyFile() {
+            return;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, mock(WindowManagerInternal.class));
+
+        mUsers = new ArrayList<>();
+        mUsers.add(new UserInfo(0, "system", 0));
+        mUsers.add(new UserInfo(10, "second", 0));
+        when(mUm.getUsers()).thenReturn(mUsers);
+
+        mService = new TestableNotificationManagerService(mContext);
+        mRoleObserver = mService.new RoleObserver(mRoleManager, mExecutor);
+
+        try {
+            mService.init(mock(Looper.class),
+                    mock(IPackageManager.class), mock(PackageManager.class),
+                    mock(LightsManager.class),
+                    mock(NotificationListeners.class), mock(NotificationAssistants.class),
+                    mock(ConditionProviders.class), mock(ICompanionDeviceManager.class),
+                    mock(SnoozeHelper.class), mock(NotificationUsageStats.class),
+                    mock(AtomicFile.class), mock(ActivityManager.class),
+                    mock(GroupHelper.class), mock(IActivityManager.class),
+                    mock(UsageStatsManagerInternal.class),
+                    mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
+                    mock(UriGrantsManagerInternal.class),
+                    mock(AppOpsManager.class), mUm);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+        mService.setPreferencesHelper(mPreferencesHelper);
+    }
+
+    @Test
+    public void testInit() {
+        List<String> dialer0 = new ArrayList<>();
+        dialer0.add("dialer");
+        List<String> emer0 = new ArrayList<>();
+        emer0.add("emergency");
+        List<String> sms10 = new ArrayList<>();
+        sms10.add("sms");
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_DIALER,
+                mUsers.get(0).getUserHandle())).
+                thenReturn(dialer0);
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_EMERGENCY,
+                mUsers.get(0).getUserHandle())).
+                thenReturn(emer0);
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_SMS,
+                mUsers.get(1).getUserHandle())).
+                thenReturn(sms10);
+
+        mRoleObserver.init();
+
+        // verify internal records of current state of the world
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_DIALER, dialer0.get(0), mUsers.get(0).id));
+        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_DIALER, dialer0.get(0), mUsers.get(1).id));
+        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_SMS, dialer0.get(0), mUsers.get(1).id));
+
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).id));
+        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).id));
+
+        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_SMS, sms10.get(0), mUsers.get(0).id));
+        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_DIALER, sms10.get(0), mUsers.get(0).id));
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
+                ROLE_SMS, sms10.get(0), mUsers.get(1).id));
+
+        // make sure we're listening to updates
+        verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(
+                eq(mExecutor), any(), eq(UserHandle.ALL));
+
+        // make sure we told pref helper about the state of the world
+        verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, new ArraySet<>(dialer0));
+        verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, new ArraySet<>(emer0));
+        verify(mPreferencesHelper, times(1)).updateDefaultApps(10, null, new ArraySet<>(sms10));
+    }
+
+    @Test
+    public void testSwapDefault() {
+        List<String> dialer0 = new ArrayList<>();
+        dialer0.add("dialer");
+
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_DIALER,
+                mUsers.get(0).getUserHandle())).
+                thenReturn(dialer0);
+
+        mRoleObserver.init();
+
+        List<String> newDefault = new ArrayList<>();
+        newDefault.add("phone");
+
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_DIALER,
+                mUsers.get(0).getUserHandle())).
+                thenReturn(newDefault);
+
+        mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0));
+
+        verify(mPreferencesHelper, times(1)).updateDefaultApps(
+                0, new ArraySet<>(dialer0), new ArraySet<>(newDefault));
+    }
+
+    @Test
+    public void testSwapDefault_multipleOverlappingApps() {
+        List<String> dialer0 = new ArrayList<>();
+        dialer0.add("dialer");
+        dialer0.add("phone");
+
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_DIALER,
+                mUsers.get(0).getUserHandle())).
+                thenReturn(dialer0);
+
+        mRoleObserver.init();
+
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 0));
+        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "emerPhone", 0));
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
+
+        List<String> newDefault = new ArrayList<>();
+        newDefault.add("phone");
+        newDefault.add("emerPhone");
+
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_DIALER,
+                mUsers.get(0).getUserHandle())).
+                thenReturn(newDefault);
+
+        mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0));
+
+        ArraySet<String> expectedRemove = new ArraySet<>();
+        expectedRemove.add("dialer");
+        ArraySet<String> expectedAdd = new ArraySet<>();
+        expectedAdd.add("emerPhone");
+
+        verify(mPreferencesHelper, times(1)).updateDefaultApps(
+                0, expectedRemove, expectedAdd);
+
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 0));
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "emerPhone", 0));
+        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
+    }
+
+    @Test
+    public void testSwapDefault_newUser() {
+        List<String> dialer0 = new ArrayList<>();
+        dialer0.add("dialer");
+
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_DIALER,
+                mUsers.get(0).getUserHandle())).
+                thenReturn(dialer0);
+
+        mRoleObserver.init();
+
+        List<String> dialer10 = new ArrayList<>();
+        dialer10.add("phone");
+
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_DIALER,
+                mUsers.get(1).getUserHandle())).
+                thenReturn(dialer10);
+
+        mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(10));
+
+        ArraySet<String> expectedRemove = new ArraySet<>();
+        ArraySet<String> expectedAdd = new ArraySet<>();
+        expectedAdd.add("phone");
+
+        verify(mPreferencesHelper, times(1)).updateDefaultApps(
+                10, expectedRemove, expectedAdd);
+
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 10));
+        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
+    }
+}