Migrate managed services from setting to policy xml

Change-Id: Ie20f91dbdd0ba6b57b5909cbf0152a32754fe02d
Fixes: 62263757
Test: runtest systemui-notification, cts AudioManagerTest,
cts-verifier DND tests, verify bug reports after toggling
access for various types of managed services, verified
default approved services aren't renabled on boot; verified that
they are reenabled after a device reset, verified that
settings are migrated after a restore from OC backup.
diff --git a/api/current.txt b/api/current.txt
index 52005cb..c1b025a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5592,6 +5592,7 @@
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
+    method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
     method public void notify(java.lang.String, int, android.app.Notification);
diff --git a/api/system-current.txt b/api/system-current.txt
index c1c1a0f..83b5bae 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5798,6 +5798,7 @@
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
+    method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
     method public void notify(java.lang.String, int, android.app.Notification);
diff --git a/api/test-current.txt b/api/test-current.txt
index 169bea0..debb106 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5606,6 +5606,7 @@
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
+    method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
     method public void notify(java.lang.String, int, android.app.Notification);
@@ -35028,7 +35029,7 @@
     field public static final java.lang.String DISABLED_PRINT_SERVICES = "disabled_print_services";
     field public static final java.lang.String ENABLED_ACCESSIBILITY_SERVICES = "enabled_accessibility_services";
     field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods";
-    field public static final java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
+    field public static final deprecated java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
     field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
     field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
     field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 1c1883b..08821be 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -116,6 +116,16 @@
     boolean matchesCallFilter(in Bundle extras);
     boolean isSystemConditionProviderEnabled(String path);
 
+    boolean isNotificationListenerAccessGranted(in ComponentName listener);
+    boolean isNotificationListenerAccessGrantedForUser(in ComponentName listener, int userId);
+    boolean isNotificationAssistantAccessGranted(in ComponentName assistant);
+    void setNotificationListenerAccessGranted(in ComponentName listener, boolean enabled);
+    void setNotificationAssistantAccessGranted(in ComponentName assistant, boolean enabled);
+    void setNotificationListenerAccessGrantedForUser(in ComponentName listener, int userId, boolean enabled);
+    void setNotificationAssistantAccessGrantedForUser(in ComponentName assistant, int userId, boolean enabled);
+    List<String> getEnabledNotificationListenerPackages();
+    List<ComponentName> getEnabledNotificationListeners(int userId);
+
     int getZenMode();
     ZenModeConfig getZenModeConfig();
     oneway void setZenMode(int mode, in Uri conditionId, String reason);
@@ -123,7 +133,6 @@
     boolean isNotificationPolicyAccessGranted(String pkg);
     NotificationManager.Policy getNotificationPolicy(String pkg);
     void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
-    String[] getPackagesRequestingNotificationPolicyAccess();
     boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
     void setNotificationPolicyAccessGranted(String pkg, boolean granted);
     AutomaticZenRule getAutomaticZenRule(String id);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 235b8d4..a519125 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -18,14 +18,12 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.Notification.Builder;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
@@ -33,7 +31,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -41,10 +38,8 @@
 import android.os.StrictMode;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
-import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -747,14 +742,14 @@
     }
 
     /**
-     * Checks the ability to read/modify notification policy for the calling package.
+     * Checks the ability to read/modify notification do not disturb policy for the calling package.
      *
      * <p>
      * Returns true if the calling package can read/modify notification policy.
      *
      * <p>
-     * Request policy access by sending the user to the activity that matches the system intent
-     * action {@link android.provider.Settings#ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS}.
+     * Apps can request policy access by sending the user to the activity that matches the system
+     * intent action {@link android.provider.Settings#ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS}.
      *
      * <p>
      * Use {@link #ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED} to listen for
@@ -769,6 +764,39 @@
         }
     }
 
+    /**
+     * Checks whether the user has approved a given
+     * {@link android.service.notification.NotificationListenerService}.
+     *
+     * <p>
+     * The listener service must belong to the calling app.
+     *
+     * <p>
+     * Apps can request notification listener access by sending the user to the activity that
+     * matches the system intent action
+     * {@link android.provider.Settings#ACTION_NOTIFICATION_LISTENER_SETTINGS}.
+     */
+    public boolean isNotificationListenerAccessGranted(ComponentName listener) {
+        INotificationManager service = getService();
+        try {
+            return service.isNotificationListenerAccessGranted(listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
+        INotificationManager service = getService();
+        try {
+            return service.isNotificationAssistantAccessGranted(assistant);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {
         INotificationManager service = getService();
@@ -780,6 +808,18 @@
     }
 
     /**
+     * @hide
+     */
+    public List<String> getEnabledNotificationListenerPackages() {
+        INotificationManager service = getService();
+        try {
+            return service.getEnabledNotificationListenerPackages();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the current notification policy.
      *
      * <p>
@@ -825,21 +865,34 @@
     }
 
     /** @hide */
-    public ArraySet<String> getPackagesRequestingNotificationPolicyAccess() {
+    public void setNotificationListenerAccessGranted(ComponentName listener, boolean granted) {
         INotificationManager service = getService();
         try {
-            final String[] pkgs = service.getPackagesRequestingNotificationPolicyAccess();
-            if (pkgs != null && pkgs.length > 0) {
-                final ArraySet<String> rt = new ArraySet<>(pkgs.length);
-                for (int i = 0; i < pkgs.length; i++) {
-                    rt.add(pkgs[i]);
-                }
-                return rt;
-            }
+            service.setNotificationListenerAccessGranted(listener, granted);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        return new ArraySet<>();
+    }
+
+    /** @hide */
+    public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
+            boolean granted) {
+        INotificationManager service = getService();
+        try {
+            service.setNotificationListenerAccessGrantedForUser(listener, userId, granted);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public List<ComponentName> getEnabledNotificationListeners(int userId) {
+        INotificationManager service = getService();
+        try {
+            return service.getEnabledNotificationListeners(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     private Context mContext;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ed2b0b2..f5b5006 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -31,6 +31,7 @@
 import android.app.AppOpsManager;
 import android.app.Application;
 import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.SearchManager;
 import android.app.WallpaperManager;
 import android.content.ComponentName;
@@ -6607,28 +6608,36 @@
         public static final String ASSIST_DISCLOSURE_ENABLED = "assist_disclosure_enabled";
 
         /**
-         * Name of the service components that the current user has explicitly allowed to
+         * Read only list of the service components that the current user has explicitly allowed to
          * see and assist with all of the user's notifications.
          *
+         * @deprecated Use
+         * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)}.
          * @hide
          */
+        @Deprecated
         public static final String ENABLED_NOTIFICATION_ASSISTANT =
                 "enabled_notification_assistant";
 
         /**
-         * Names of the service components that the current user has explicitly allowed to
+         * Read only list of the service components that the current user has explicitly allowed to
          * see all of the user's notifications, separated by ':'.
          *
          * @hide
+         * @deprecated Use
+         * {@link NotificationManager#isNotificationAssistantAccessGranted(ComponentName)}.
          */
+        @Deprecated
         public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
 
         /**
-         * Names of the packages that the current user has explicitly allowed to
-         * manage notification policy configuration, separated by ':'.
+         * Read only list of the packages that the current user has explicitly allowed to
+         * manage do not disturb, separated by ':'.
          *
+         * @deprecated Use {@link NotificationManager#isNotificationPolicyAccessGranted()}.
          * @hide
          */
+        @Deprecated
         @TestApi
         public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES =
                 "enabled_notification_policy_access_packages";
@@ -7049,7 +7058,6 @@
             AUTOFILL_SERVICE,
             ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
             ENABLED_ACCESSIBILITY_SERVICES,
-            ENABLED_NOTIFICATION_LISTENERS,
             ENABLED_VR_LISTENERS,
             ENABLED_INPUT_METHODS,
             TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
@@ -7128,6 +7136,9 @@
 
         /** @hide */
         public static final String[] LEGACY_RESTORE_SETTINGS = {
+                ENABLED_NOTIFICATION_LISTENERS,
+                ENABLED_NOTIFICATION_ASSISTANT,
+                ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES
         };
 
         /**
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 7dc72db7..53ba9f7 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -17,24 +17,25 @@
 package android.provider;
 
 import static com.google.android.collect.Sets.newHashSet;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.is;
+
 import static java.lang.reflect.Modifier.isFinal;
 import static java.lang.reflect.Modifier.isPublic;
 import static java.lang.reflect.Modifier.isStatic;
 
-import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.lang.reflect.Field;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 /** Tests that ensure appropriate settings are backed up. */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -428,6 +429,7 @@
                  Settings.Secure.DOZE_ALWAYS_ON,
                  Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
                  Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                  Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
                  Settings.Secure.ENABLED_PRINT_SERVICES,
                  Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1df2c80..1bd58ad 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3166,33 +3166,7 @@
                 }
 
                 if (currentVersion == 128) {
-                    // Version 128: Allow OEMs to grant DND access to default apps. Note that
-                    // the new apps are appended to the list of already approved apps.
-                    final SettingsState systemSecureSettings =
-                            getSecureSettingsLocked(userId);
-
-                    final Setting policyAccess = systemSecureSettings.getSettingLocked(
-                            Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES);
-                    String defaultPolicyAccess = getContext().getResources().getString(
-                            com.android.internal.R.string.config_defaultDndAccessPackages);
-                    if (!TextUtils.isEmpty(defaultPolicyAccess)) {
-                        if (policyAccess.isNull()) {
-                            systemSecureSettings.insertSettingLocked(
-                                    Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                                    defaultPolicyAccess, null, true,
-                                    SettingsState.SYSTEM_PACKAGE_NAME);
-                        } else {
-                            StringBuilder currentSetting =
-                                    new StringBuilder(policyAccess.getValue());
-                            currentSetting.append(":");
-                            currentSetting.append(defaultPolicyAccess);
-                            systemSecureSettings.updateSettingLocked(
-                                    Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                                    currentSetting.toString(), null, true,
-                                    SettingsState.SYSTEM_PACKAGE_NAME);
-                        }
-                    }
-
+                    // Version 128: Removed
                     currentVersion = 129;
                 }
 
@@ -3365,58 +3339,7 @@
                 }
 
                 if (currentVersion == 140) {
-                    // Version 141: One-time grant of notification listener privileges
-                    // to packages specified in overlay.
-                    String defaultListenerAccess = getContext().getResources().getString(
-                            com.android.internal.R.string.config_defaultListenerAccessPackages);
-                    if (defaultListenerAccess != null) {
-                        StringBuffer newListeners = new StringBuffer();
-                        for (String whitelistPkg : defaultListenerAccess.split(":")) {
-                            // Gather all notification listener components for candidate pkgs.
-                            Intent serviceIntent =
-                                    new Intent(NotificationListenerService.SERVICE_INTERFACE)
-                                            .setPackage(whitelistPkg);
-                            List<ResolveInfo> installedServices =
-                                    getContext().getPackageManager().queryIntentServicesAsUser(
-                                            serviceIntent,
-                                            PackageManager.GET_SERVICES
-                                                    | PackageManager.GET_META_DATA
-                                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                                            userId);
-
-                            for (int i = 0, count = installedServices.size(); i < count; i++) {
-                                ResolveInfo resolveInfo = installedServices.get(i);
-                                ServiceInfo info = resolveInfo.serviceInfo;
-                                if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE
-                                        .equals(info.permission)) {
-                                    continue;
-                                }
-                                newListeners.append(":")
-                                        .append(info.getComponentName().flattenToString());
-                            }
-                        }
-
-                        if (newListeners.length() > 0) {
-                            final SettingsState secureSetting = getSecureSettingsLocked(userId);
-                            final Setting existingSetting = secureSetting.getSettingLocked(
-                                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-                            if (existingSetting.isNull()) {
-                                secureSetting.insertSettingLocked(
-                                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                                        newListeners.toString(), null, true,
-                                        SettingsState.SYSTEM_PACKAGE_NAME);
-                            } else {
-                                StringBuilder currentSetting =
-                                        new StringBuilder(existingSetting.getValue());
-                                currentSetting.append(newListeners.toString());
-                                secureSetting.updateSettingLocked(
-                                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                                        currentSetting.toString(), null, true,
-                                        SettingsState.SYSTEM_PACKAGE_NAME);
-                            }
-                        }
-                    }
+                    // Version 141: Removed
                     currentVersion = 141;
                 }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 38c6157..67920ed 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,10 +16,8 @@
 
 package com.android.server.media;
 
-import android.Manifest;
-import android.annotation.NonNull;
-import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.INotificationManager;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
@@ -57,13 +55,11 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.speech.RecognizerIntent;
 import android.text.TextUtils;
-import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -80,7 +76,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -112,6 +107,7 @@
     private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
     private SettingsObserver mSettingsObserver;
+    private INotificationManager mNotificationManager;
 
     // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
     // It's always not null after the MediaSessionService is started.
@@ -129,6 +125,8 @@
         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
         mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
+        mNotificationManager = INotificationManager.Stub.asInterface(
+                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
     }
 
     @Override
@@ -471,28 +469,11 @@
             Log.d(TAG, "Checking if enabled notification listener " + compName);
         }
         if (compName != null) {
-            final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
-                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                    userId);
-            if (enabledNotifListeners != null) {
-                final String[] components = enabledNotifListeners.split(":");
-                for (int i = 0; i < components.length; i++) {
-                    final ComponentName component =
-                            ComponentName.unflattenFromString(components[i]);
-                    if (component != null) {
-                        if (compName.equals(component)) {
-                            if (DEBUG) {
-                                Log.d(TAG, "ok to get sessions. " + component +
-                                        " is authorized notification listener");
-                            }
-                            return true;
-                        }
-                    }
-                }
-            }
-            if (DEBUG) {
-                Log.d(TAG, "not ok to get sessions. " + compName +
-                        " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
+            try {
+                return mNotificationManager.isNotificationListenerAccessGrantedForUser(
+                        compName, userId);
+            } catch(RemoteException e) {
+                Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
             }
         }
         return false;
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index c28fb67..5cc14b5 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -16,14 +16,12 @@
 
 package com.android.server.notification;
 
-import android.annotation.NonNull;
 import android.app.INotificationManager;
 import android.app.NotificationManager;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.net.Uri;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.RemoteException;
@@ -32,7 +30,6 @@
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
 import android.service.notification.IConditionProvider;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -45,6 +42,9 @@
 import java.util.Arrays;
 
 public class ConditionProviders extends ManagedServices {
+
+    private static final String TAG_ENABLED_DND_APPS = "dnd_apps";
+
     private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
     private final ArraySet<String> mSystemConditionProviderNames;
     private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
@@ -52,11 +52,12 @@
 
     private Callback mCallback;
 
-    public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
-        super(context, handler, new Object(), userProfiles);
+    public ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm) {
+        super(context, new Object(), userProfiles, pm);
         mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
                 "system.condition.providers",
                 R.array.config_system_condition_providers));
+        mApprovalLevel = APPROVAL_BY_PACKAGE;
     }
 
     public void setCallback(Callback callback) {
@@ -83,6 +84,7 @@
         c.caption = "condition provider";
         c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
         c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
+        c.managedServiceTypeTag = TAG_ENABLED_DND_APPS;
         c.secondarySettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
         c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
         c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
@@ -164,7 +166,7 @@
     }
 
     @Override
-    public void onPackagesChanged(boolean removingPackage, String[] pkgList) {
+    public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) {
         if (removingPackage) {
             INotificationManager inm = NotificationManager.getService();
 
@@ -179,7 +181,7 @@
                 }
             }
         }
-        super.onPackagesChanged(removingPackage, pkgList);
+        super.onPackagesChanged(removingPackage, pkgList, uid);
     }
 
     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
@@ -277,31 +279,6 @@
         }
     }
 
-    @Override
-    protected @NonNull ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
-            int userId) {
-        final ContentResolver cr = mContext.getContentResolver();
-        String settingValue = Settings.Secure.getStringForUser(
-                cr,
-                settingName,
-                userId);
-        if (TextUtils.isEmpty(settingValue))
-            return new ArraySet<>();
-        String[] packages = settingValue.split(ENABLED_SERVICES_SEPARATOR);
-        ArraySet<ComponentName> result = new ArraySet<>(packages.length);
-        for (int i = 0; i < packages.length; i++) {
-            if (!TextUtils.isEmpty(packages[i])) {
-                final ComponentName component = ComponentName.unflattenFromString(packages[i]);
-                if (component != null) {
-                    result.addAll(queryPackageForServices(component.getPackageName(), userId));
-                } else {
-                    result.addAll(queryPackageForServices(packages[i], userId));
-                }
-            }
-        }
-        return result;
-    }
-
     public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
         synchronized (mMutex) {
             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 73a365b..2f88740 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -25,12 +25,10 @@
 import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -39,19 +37,16 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
@@ -59,13 +54,18 @@
 
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Manages the lifecycle of application-provided services bound by system server.
@@ -83,66 +83,60 @@
 
     protected static final String ENABLED_SERVICES_SEPARATOR = ":";
 
+    /**
+     * List of components and apps that can have running {@link ManagedServices}.
+     */
+    static final String TAG_MANAGED_SERVICES = "service_listing";
+    static final String ATT_APPROVED_LIST = "approved";
+    static final String ATT_USER_ID = "user";
+    static final String ATT_IS_PRIMARY = "primary";
+
+    static final int APPROVAL_BY_PACKAGE = 0;
+    static final int APPROVAL_BY_COMPONENT = 1;
+
     protected final Context mContext;
     protected final Object mMutex;
     private final UserProfiles mUserProfiles;
-    private final SettingsObserver mSettingsObserver;
     private final IPackageManager mPm;
     private final Config mConfig;
-    private ArraySet<String> mRestored;
 
     // contains connections to all connected services, including app services
     // and system services
-    private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();
+    private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<>();
     // things that will be put into mServices as soon as they're ready
-    private final ArrayList<String> mServicesBinding = new ArrayList<String>();
+    private final ArrayList<String> mServicesBinding = new ArrayList<>();
     // lists the component names of all enabled (and therefore potentially connected)
     // app services for current profiles.
     private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
-            = new ArraySet<ComponentName>();
+            = new ArraySet<>();
     // Just the packages from mEnabledServicesForCurrentProfiles
-    private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>();
-    // List of packages in restored setting across all mUserProfiles, for quick
-    // filtering upon package updates.
-    private ArraySet<String> mRestoredPackages = new ArraySet<>();
+    private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
     // List of enabled packages that have nevertheless asked not to be run
     private ArraySet<ComponentName> mSnoozingForCurrentProfiles = new ArraySet<>();
 
+    // List of approved packages or components (by user, then by primary/secondary) that are
+    // allowed to be bound as managed services. A package or component appearing in this list does
+    // not mean that we are currently bound to said package/component.
+    private ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = new ArrayMap<>();
 
     // Kept to de-dupe user change events (experienced after boot, when we receive a settings and a
     // user change).
     private int[] mLastSeenProfileIds;
 
-    private final BroadcastReceiver mRestoreReceiver;
+    // True if approved services are stored in xml, not settings.
+    private boolean mUseXml;
 
-    public ManagedServices(Context context, Handler handler, Object mutex,
-            UserProfiles userProfiles) {
+    // Whether managed services are approved individually or package wide
+    protected int mApprovalLevel;
+
+    public ManagedServices(Context context, Object mutex, UserProfiles userProfiles,
+            IPackageManager pm) {
         mContext = context;
         mMutex = mutex;
         mUserProfiles = userProfiles;
-        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+        mPm = pm;
         mConfig = getConfig();
-        mSettingsObserver = new SettingsObserver(handler);
-
-        mRestoreReceiver = new SettingRestoredReceiver();
-        IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
-        context.registerReceiver(mRestoreReceiver, filter);
-        rebuildRestoredPackages();
-    }
-
-    class SettingRestoredReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
-                String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
-                if (Objects.equals(element, mConfig.secureSettingName)
-                        || Objects.equals(element, mConfig.secondarySettingName)) {
-                    String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE);
-                    String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
-                    settingRestored(element, prevValue, newValue, getSendingUserId());
-                }
-            }
-        }
+        mApprovalLevel = APPROVAL_BY_COMPONENT;
     }
 
     abstract protected Config getConfig();
@@ -167,17 +161,33 @@
     protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
 
     private ManagedServiceInfo newServiceInfo(IInterface service,
-            ComponentName component, int userid, boolean isSystem, ServiceConnection connection,
+            ComponentName component, int userId, boolean isSystem, ServiceConnection connection,
             int targetSdkVersion) {
-        return new ManagedServiceInfo(service, component, userid, isSystem, connection,
+        return new ManagedServiceInfo(service, component, userId, isSystem, connection,
                 targetSdkVersion);
     }
 
-    public void onBootPhaseAppsCanStart() {
-        mSettingsObserver.observe();
-    }
+    public void onBootPhaseAppsCanStart() {}
 
     public void dump(PrintWriter pw, DumpFilter filter) {
+        pw.println("    Allowed " + getCaption() + "s:");
+        final int N = mApproved.size();
+        for (int i = 0 ; i < N; i++) {
+            final int userId = mApproved.keyAt(i);
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+            if (approvedByType != null) {
+                final int M = approvedByType.size();
+                for (int j = 0; j < M; j++) {
+                    final boolean isPrimary = approvedByType.keyAt(j);
+                    final ArraySet<String> approved = approvedByType.valueAt(j);
+                    if (approvedByType != null && approvedByType.size() > 0) {
+                        pw.println("      " + String.join(ENABLED_SERVICES_SEPARATOR, approved)
+                                + " (user: " + userId + " isPrimary: " + isPrimary + ")");
+                    }
+                }
+            }
+        }
+
         pw.println("    All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
                 + ") enabled for current profiles:");
         for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
@@ -201,67 +211,240 @@
         }
     }
 
-    // By convention, restored settings are replicated to another settings
-    // entry, named similarly but with a disambiguation suffix.
-    public static String restoredSettingName(String setting) {
-        return setting + ":restored";
-    }
-
-    // The OS has done a restore of this service's saved state.  We clone it to the
-    // 'restored' reserve, and then once we return and the actual write to settings is
-    // performed, our observer will do the work of maintaining the restored vs live
-    // settings data.
-    public void settingRestored(String element, String oldValue, String newValue, int userid) {
-        if (DEBUG) Slog.d(TAG, "Restored managed service setting: " + element
-                + " ovalue=" + oldValue + " nvalue=" + newValue);
-        if (mConfig.secureSettingName.equals(element) ||
-                mConfig.secondarySettingName.equals(element)) {
-            if (element != null) {
-                Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                        restoredSettingName(element),
-                        newValue,
-                        userid);
-                if (mConfig.secureSettingName.equals(element)) {
-                    updateSettingsAccordingToInstalledServices(element, userid);
-                }
-                rebuildRestoredPackages();
+    protected void onSettingRestored(String element, String value, int userId) {
+        if (!mUseXml) {
+            Slog.d(TAG, "Restored managed service setting: " + element);
+            if (mConfig.secureSettingName.equals(element) ||
+                    (mConfig.secondarySettingName != null
+                            && mConfig.secondarySettingName.equals(element))) {
+                Settings.Secure.putStringForUser(
+                        mContext.getContentResolver(), element, value, userId);
+                loadAllowedComponentsFromSettings();
+                rebindServices(false);
             }
         }
     }
 
-    public boolean isComponentEnabledForPackage(String pkg) {
+    public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
+        out.startTag(null, getConfig().managedServiceTypeTag);
+
+        if (forBackup) {
+            trimApprovedListsAccordingToInstalledServices();
+        }
+
+        final int N = mApproved.size();
+        for (int i = 0 ; i < N; i++) {
+            final int userId = mApproved.keyAt(i);
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+            if (approvedByType != null) {
+                final int M = approvedByType.size();
+                for (int j = 0; j < M; j++) {
+                    final boolean isPrimary = approvedByType.keyAt(j);
+                    final Set<String> approved = approvedByType.valueAt(j);
+                    if (approved != null && approved.size() > 0) {
+                        String allowedItems = String.join(ENABLED_SERVICES_SEPARATOR, approved);
+                        out.startTag(null, TAG_MANAGED_SERVICES);
+                        out.attribute(null, ATT_APPROVED_LIST, allowedItems);
+                        out.attribute(null, ATT_USER_ID, Integer.toString(userId));
+                        out.attribute(null, ATT_IS_PRIMARY, Boolean.toString(isPrimary));
+                        out.endTag(null, TAG_MANAGED_SERVICES);
+
+                        if (!forBackup && isPrimary) {
+                            // Also write values to settings, for observers who haven't migrated yet
+                            Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                                    getConfig().secureSettingName, allowedItems, userId);
+                        }
+
+                    }
+                }
+            }
+        }
+
+        out.endTag(null, getConfig().managedServiceTypeTag);
+    }
+
+    /**
+     * @return false if modifications were made to the data on load that requires the xml file
+     * to be re-written
+     */
+    public boolean readXml(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        boolean rewriteXml = false;
+        int type = parser.getEventType();
+        String tag = parser.getName();
+        if (type != XmlPullParser.START_TAG || !getConfig().managedServiceTypeTag.equals(tag)) {
+            // xml empty/invalid - read from setting instead
+            loadAllowedComponentsFromSettings();
+            rewriteXml = true;
+        } else {
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                tag = parser.getName();
+                if (type == XmlPullParser.END_TAG
+                        && getConfig().managedServiceTypeTag.equals(tag)) {
+                    break;
+                }
+                if (type == XmlPullParser.START_TAG) {
+                    if (TAG_MANAGED_SERVICES.equals(tag)) {
+                        final String approved = XmlUtils.safeString(parser, ATT_APPROVED_LIST, "");
+                        final int userId = XmlUtils.safeInt(parser, ATT_USER_ID, 0);
+                        final boolean isPrimary = XmlUtils.safeBool(parser, ATT_IS_PRIMARY, true);
+                        addApprovedList(approved, userId, isPrimary);
+                    }
+                }
+            }
+            mUseXml = true;
+        }
+        rebindServices(false);
+
+        return rewriteXml;
+    }
+
+    private void loadAllowedComponentsFromSettings() {
+
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        for (UserInfo user : userManager.getUsers()) {
+            final ContentResolver cr = mContext.getContentResolver();
+            addApprovedList(Settings.Secure.getStringForUser(
+                    cr,
+                    getConfig().secureSettingName,
+                    user.id), user.id, true);
+            if (!TextUtils.isEmpty(getConfig().secondarySettingName)) {
+                addApprovedList(Settings.Secure.getStringForUser(
+                        cr,
+                        getConfig().secondarySettingName,
+                        user.id), user.id, false);
+            }
+        }
+        Slog.d(TAG, "Done loading approved values from settings");
+    }
+
+    private void addApprovedList(String approved, int userId, boolean isPrimary) {
+        if (TextUtils.isEmpty(approved)) {
+            approved = "";
+        }
+        ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+        if (approvedByType == null) {
+            approvedByType = new ArrayMap<>();
+            mApproved.put(userId, approvedByType);
+        }
+        String[] approvedArray = approved.split(ENABLED_SERVICES_SEPARATOR);
+        final ArraySet<String> approvedList = new ArraySet<>();
+        for (String pkgOrComponent : approvedArray) {
+            String approvedItem = getApprovedValue(pkgOrComponent);
+            if (approvedItem != null) {
+                approvedList.add(approvedItem);
+            }
+        }
+        approvedByType.put(isPrimary, approvedList);
+    }
+
+    protected boolean isComponentEnabledForPackage(String pkg) {
         return mEnabledServicesPackageNames.contains(pkg);
     }
 
-    public void onPackagesChanged(boolean removingPackage, String[] pkgList) {
-        if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
-                + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
-                + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
-        boolean anyServicesInvolved = false;
+    protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
+            boolean isPrimary, boolean enabled) {
+        ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.get(userId);
+        if (allowedByType == null) {
+            allowedByType = new ArrayMap<>();
+            mApproved.put(userId, allowedByType);
+        }
+        ArraySet<String> approved = allowedByType.get(isPrimary);
+        if (approved == null) {
+            approved = new ArraySet<>();
+            allowedByType.put(isPrimary, approved);
+        }
+        String approvedItem = getApprovedValue(pkgOrComponent);
 
-        if (pkgList != null && (pkgList.length > 0)) {
-            for (String pkgName : pkgList) {
-                if (mEnabledServicesPackageNames.contains(pkgName) ||
-                        mRestoredPackages.contains(pkgName)) {
-                    anyServicesInvolved = true;
-                }
+        if (approvedItem != null) {
+            if (enabled) {
+                approved.add(approvedItem);
+            } else {
+                approved.remove(approvedItem);
             }
         }
 
-        if (anyServicesInvolved) {
-            // if we're not replacing a package, clean up orphaned bits
-            if (removingPackage) {
-                updateSettingsAccordingToInstalledServices();
-                rebuildRestoredPackages();
+        rebindServices(false);
+    }
+
+    private String getApprovedValue(String pkgOrComponent) {
+        if (mApprovalLevel == APPROVAL_BY_COMPONENT) {
+            if(ComponentName.unflattenFromString(pkgOrComponent) != null) {
+                return pkgOrComponent;
             }
-            // make sure we're still bound to any of our services who may have just upgraded
-            rebindServices(false);
+            return null;
+        } else {
+            return getPackageName(pkgOrComponent);
+        }
+    }
+
+    protected List<ComponentName> getAllowedComponents(int userId) {
+        final List<ComponentName> allowedComponents = new ArrayList<>();
+        final ArrayMap<Boolean, ArraySet<String>> allowedByType =
+                mApproved.getOrDefault(userId, new ArrayMap<>());
+        for (int i = 0; i < allowedByType.size(); i++) {
+            final ArraySet<String> allowed = allowedByType.valueAt(i);
+            allowedComponents.addAll(allowed.stream().map(ComponentName::unflattenFromString)
+                    .filter(out -> out != null).collect(Collectors.toList()));
+        }
+        return allowedComponents;
+    }
+
+    protected List<String> getAllowedPackages(int userId) {
+        final List<String> allowedPackages = new ArrayList<>();
+        final ArrayMap<Boolean, ArraySet<String>> allowedByType =
+                mApproved.getOrDefault(userId, new ArrayMap<>());
+        for (int i = 0; i < allowedByType.size(); i++) {
+            final ArraySet<String> allowed = allowedByType.valueAt(i);
+            allowedPackages.addAll(
+                    allowed.stream().map(this::getPackageName).collect(Collectors.toList()));
+        }
+        return allowedPackages;
+    }
+
+    protected boolean isPackageOrComponentAllowed(String pkgOrComponent, int userId) {
+        ArrayMap<Boolean, ArraySet<String>> allowedByType =
+                mApproved.getOrDefault(userId, new ArrayMap<>());
+        for (int i = 0; i < allowedByType.size(); i++) {
+            ArraySet<String> allowed = allowedByType.valueAt(i);
+            if (allowed.contains(pkgOrComponent)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
+        if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
+                + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
+                + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
+
+        if (pkgList != null && (pkgList.length > 0)) {
+            boolean anyServicesInvolved = false;
+            // Remove notification settings for uninstalled package
+            if (removingPackage) {
+                int size = Math.min(pkgList.length, uidList.length);
+                for (int i = 0; i < size; i++) {
+                    final String pkg = pkgList[i];
+                    final int userId = UserHandle.getUserId(uidList[i]);
+                    anyServicesInvolved = removeUninstalledItemsFromApprovedLists(userId, pkg);
+                }
+            }
+            for (String pkgName : pkgList) {
+                if (mEnabledServicesPackageNames.contains(pkgName)) {
+                    anyServicesInvolved = true;
+                }
+            }
+
+            if (anyServicesInvolved) {
+                // make sure we're still bound to any of our services who may have just upgraded
+                rebindServices(false);
+            }
         }
     }
 
     public void onUserSwitched(int user) {
         if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
-        rebuildRestoredPackages();
         if (Arrays.equals(mLastSeenProfileIds, mUserProfiles.getCurrentProfileIds())) {
             if (DEBUG) Slog.d(TAG, "Current profile IDs didn't change, skipping rebindServices().");
             return;
@@ -271,11 +454,10 @@
 
     public void onUserUnlocked(int user) {
         if (DEBUG) Slog.d(TAG, "onUserUnlocked u=" + user);
-        rebuildRestoredPackages();
         rebindServices(false);
     }
 
-    public ManagedServiceInfo getServiceFromTokenLocked(IInterface service) {
+    private ManagedServiceInfo getServiceFromTokenLocked(IInterface service) {
         if (service == null) {
             return null;
         }
@@ -288,7 +470,7 @@
         return null;
     }
 
-    public ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
+    protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
         checkNotNull(service);
         ManagedServiceInfo info = getServiceFromTokenLocked(service);
         if (info != null) {
@@ -315,9 +497,9 @@
 
     /**
      * Add a service to our callbacks. The lifecycle of this service is managed externally,
-     * but unlike a system service, it should not be considered privledged.
+     * but unlike a system service, it should not be considered privileged.
      * */
-    public void registerGuestService(ManagedServiceInfo guest) {
+    protected void registerGuestService(ManagedServiceInfo guest) {
         checkNotNull(guest.service);
         if (!checkType(guest.service)) {
             throw new IllegalArgumentException();
@@ -327,7 +509,7 @@
         }
     }
 
-    public void setComponentState(ComponentName component, boolean enabled) {
+    protected void setComponentState(ComponentName component, boolean enabled) {
         boolean previous = !mSnoozingForCurrentProfiles.contains(component);
         if (previous == enabled) {
             return;
@@ -345,7 +527,6 @@
                     component.flattenToShortString());
         }
 
-
         synchronized (mMutex) {
             final int[] userIds = mUserProfiles.getCurrentProfileIds();
 
@@ -359,84 +540,31 @@
         }
     }
 
-    private void rebuildRestoredPackages() {
-        mRestoredPackages.clear();
-        String secureSettingName = restoredSettingName(mConfig.secureSettingName);
-        String secondarySettingName = mConfig.secondarySettingName == null
-                ? null : restoredSettingName(mConfig.secondarySettingName);
-        int[] userIds = mUserProfiles.getCurrentProfileIds();
-        final int N = userIds.length;
-        for (int i = 0; i < N; ++i) {
-            ArraySet<ComponentName> names =
-                    loadComponentNamesFromSetting(secureSettingName, userIds[i]);
-            if (secondarySettingName != null) {
-                names.addAll(loadComponentNamesFromSetting(secondarySettingName, userIds[i]));
-            }
-            for (ComponentName name : names) {
-                mRestoredPackages.add(name.getPackageName());
-            }
-        }
-    }
-
-
-    protected @NonNull ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
-            int userId) {
-        final ContentResolver cr = mContext.getContentResolver();
-        String settingValue = Settings.Secure.getStringForUser(
-            cr,
-            settingName,
-            userId);
-        if (TextUtils.isEmpty(settingValue))
+    private @NonNull ArraySet<ComponentName> loadComponentNamesFromValues(
+            ArraySet<String> approved, int userId) {
+        if (approved == null || approved.size() == 0)
             return new ArraySet<>();
-        String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
-        ArraySet<ComponentName> result = new ArraySet<>(restored.length);
-        for (int i = 0; i < restored.length; i++) {
-            ComponentName value = ComponentName.unflattenFromString(restored[i]);
-            if (null != value) {
-                result.add(value);
+        ArraySet<ComponentName> result = new ArraySet<>(approved.size());
+        for (int i = 0; i < approved.size(); i++) {
+            final String packageOrComponent = approved.valueAt(i);
+            if (!TextUtils.isEmpty(packageOrComponent)) {
+                ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
+                if (component != null) {
+                    result.add(component);
+                } else {
+                    result.addAll(queryPackageForServices(packageOrComponent, userId));
+                }
             }
         }
         return result;
     }
 
-    private void storeComponentsToSetting(Set<ComponentName> components,
-                                          String settingName,
-                                          int userId) {
-        String[] componentNames = null;
-        if (null != components) {
-            componentNames = new String[components.size()];
-            int index = 0;
-            for (ComponentName c: components) {
-                componentNames[index++] = c.flattenToString();
-            }
-        }
-        final String value = (componentNames == null) ? "" :
-                TextUtils.join(ENABLED_SERVICES_SEPARATOR, componentNames);
-        final ContentResolver cr = mContext.getContentResolver();
-        Settings.Secure.putStringForUser(
-            cr,
-            settingName,
-            value,
-            userId);
-    }
-
-    /**
-     * Remove access for any services that no longer exist.
-     */
-    private void updateSettingsAccordingToInstalledServices() {
-        int[] userIds = mUserProfiles.getCurrentProfileIds();
-        final int N = userIds.length;
-        for (int i = 0; i < N; ++i) {
-            updateSettingsAccordingToInstalledServices(mConfig.secureSettingName, userIds[i]);
-            if (mConfig.secondarySettingName != null) {
-                updateSettingsAccordingToInstalledServices(
-                        mConfig.secondarySettingName, userIds[i]);
-            }
-        }
-        rebuildRestoredPackages();
-    }
-
     protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
+        return queryPackageForServices(packageName, 0, userId);
+    }
+
+    protected Set<ComponentName> queryPackageForServices(String packageName, int extraFlags,
+            int userId) {
         Set<ComponentName> installed = new ArraySet<>();
         final PackageManager pm = mContext.getPackageManager();
         Intent queryIntent = new Intent(mConfig.serviceInterface);
@@ -445,7 +573,7 @@
         }
         List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
                 queryIntent,
-                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | extraFlags,
                 userId);
         if (DEBUG)
             Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
@@ -468,50 +596,73 @@
         return installed;
     }
 
-    private void updateSettingsAccordingToInstalledServices(String setting, int userId) {
-        boolean restoredChanged = false;
-        boolean currentChanged = false;
-        Set<ComponentName> restored =
-                loadComponentNamesFromSetting(restoredSettingName(setting), userId);
-        Set<ComponentName> current =
-                loadComponentNamesFromSetting(setting, userId);
-        // Load all services for all packages.
-        Set<ComponentName> installed = queryPackageForServices(null, userId);
-
-        ArraySet<ComponentName> retained = new ArraySet<>();
-
-        for (ComponentName component : installed) {
-            if (null != restored) {
-                boolean wasRestored = restored.remove(component);
-                if (wasRestored) {
-                    // Freshly installed package has service that was mentioned in restored setting.
-                    if (DEBUG)
-                        Slog.v(TAG, "Restoring " + component + " for user " + userId);
-                    restoredChanged = true;
-                    currentChanged = true;
-                    retained.add(component);
-                    continue;
+    private void trimApprovedListsAccordingToInstalledServices() {
+        int N = mApproved.size();
+        for (int i = 0 ; i < N; i++) {
+            final int userId = mApproved.keyAt(i);
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+            int M = approvedByType.size();
+            for (int j = 0; j < M; j++) {
+                final ArraySet<String> approved = approvedByType.valueAt(j);
+                int P = approved.size();
+                for (int k = P - 1; k >= 0; k--) {
+                    final String approvedPackageOrComponent = approved.valueAt(k);
+                    if (!hasMatchingServices(approvedPackageOrComponent, userId)){
+                        approved.removeAt(k);
+                        if (DEBUG) {
+                            Slog.v(TAG, "Removing " + approvedPackageOrComponent
+                                    + " from approved list; no matching services found");
+                        }
+                    } else {
+                        if (DEBUG) {
+                            Slog.v(TAG, "Keeping " + approvedPackageOrComponent
+                                    + " on approved list; matching services found");
+                        }
+                    }
                 }
             }
+        }
+    }
 
-            if (null != current) {
-                if (current.contains(component))
-                    retained.add(component);
+    private boolean removeUninstalledItemsFromApprovedLists(int uninstalledUserId, String pkg) {
+        boolean removed = false;
+        final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(uninstalledUserId);
+        if (approvedByType != null) {
+            int M = approvedByType.size();
+            for (int j = 0; j < M; j++) {
+                final ArraySet<String> approved = approvedByType.valueAt(j);
+                int O = approved.size();
+                for (int k = O - 1; k >= 0; k--) {
+                    final String packageOrComponent = approved.valueAt(k);
+                    final String packageName = getPackageName(packageOrComponent);
+                    if (TextUtils.equals(pkg, packageName)) {
+                        approved.removeAt(k);
+                        if (DEBUG) {
+                            Slog.v(TAG, "Removing " + packageOrComponent
+                                    + " from approved list; uninstalled");
+                        }
+                    }
+                }
             }
         }
+        return removed;
+    }
 
-        currentChanged |= ((current == null ? 0 : current.size()) != retained.size());
-
-        if (currentChanged) {
-            if (DEBUG) Slog.v(TAG, "List of  " + getCaption() + " services was updated " + current);
-            storeComponentsToSetting(retained, setting, userId);
+    protected String getPackageName(String packageOrComponent) {
+        final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
+        if (component != null) {
+            return component.getPackageName();
+        } else {
+            return packageOrComponent;
         }
+    }
 
-        if (restoredChanged) {
-            if (DEBUG) Slog.v(TAG,
-                    "List of  " + getCaption() + " restored services was updated " + restored);
-            storeComponentsToSetting(restored, restoredSettingName(setting), userId);
+    private boolean hasMatchingServices(String packageOrComponent, int userId) {
+        if (!TextUtils.isEmpty(packageOrComponent)) {
+            final String packageName = getPackageName(packageOrComponent);
+            return queryPackageForServices(packageName, userId).size() > 0;
         }
+        return false;
     }
 
     /**
@@ -526,11 +677,19 @@
         final SparseArray<ArraySet<ComponentName>> componentsByUser = new SparseArray<>();
 
         for (int i = 0; i < nUserIds; ++i) {
-            componentsByUser.put(userIds[i],
-                    loadComponentNamesFromSetting(mConfig.secureSettingName, userIds[i]));
-            if (mConfig.secondarySettingName != null) {
-                componentsByUser.get(userIds[i]).addAll(
-                        loadComponentNamesFromSetting(mConfig.secondarySettingName, userIds[i]));
+            final int userId = userIds[i];
+            final ArrayMap<Boolean, ArraySet<String>> approvedLists = mApproved.get(userIds[i]);
+            if (approvedLists != null) {
+                final int N = approvedLists.size();
+                for (int j = 0; j < N; j++) {
+                    ArraySet<ComponentName> approvedByUser = componentsByUser.get(userId);
+                    if (approvedByUser == null) {
+                        approvedByUser = new ArraySet<>();
+                        componentsByUser.put(userId, approvedByUser);
+                    }
+                    approvedByUser.addAll(
+                            loadComponentNamesFromValues(approvedLists.valueAt(j), userId));
+                }
             }
         }
 
@@ -552,7 +711,7 @@
                 // decode the list of components
                 final ArraySet<ComponentName> userComponents = componentsByUser.get(userIds[i]);
                 if (null == userComponents) {
-                    toAdd.put(userIds[i], new ArraySet<ComponentName>());
+                    toAdd.put(userIds[i], new ArraySet<>());
                     continue;
                 }
 
@@ -595,7 +754,7 @@
                             PackageManager.MATCH_DIRECT_BOOT_AWARE
                                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
                     if (info == null || !mConfig.bindPermission.equals(info.permission)) {
-                        Slog.w(TAG, "Skipping " + getCaption() + " service " + component
+                        Slog.w(TAG, "Not binding " + getCaption() + " service " + component
                                 + ": it does not require the permission " + mConfig.bindPermission);
                         continue;
                     }
@@ -719,7 +878,6 @@
             }
         } catch (SecurityException ex) {
             Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
-            return;
         }
     }
 
@@ -816,45 +974,6 @@
         }
     }
 
-    private class SettingsObserver extends ContentObserver {
-        private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName);
-        private final Uri mSecondarySettingsUri;
-
-        private SettingsObserver(Handler handler) {
-            super(handler);
-            if (mConfig.secondarySettingName != null) {
-                mSecondarySettingsUri = Settings.Secure.getUriFor(mConfig.secondarySettingName);
-            } else {
-                mSecondarySettingsUri = null;
-            }
-        }
-
-        private void observe() {
-            ContentResolver resolver = mContext.getContentResolver();
-            resolver.registerContentObserver(mSecureSettingsUri,
-                    false, this, UserHandle.USER_ALL);
-            if (mSecondarySettingsUri != null) {
-                resolver.registerContentObserver(mSecondarySettingsUri,
-                        false, this, UserHandle.USER_ALL);
-            }
-            update(null);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            update(uri);
-        }
-
-        private void update(Uri uri) {
-            if (uri == null || mSecureSettingsUri.equals(uri)
-                    || uri.equals(mSecondarySettingsUri)) {
-                if (DEBUG) Slog.d(TAG, "Setting changed: uri=" + uri);
-                rebindServices(false);
-                rebuildRestoredPackages();
-            }
-        }
-    }
-
     public class ManagedServiceInfo implements IBinder.DeathRecipient {
         public IInterface service;
         public ComponentName component;
@@ -1000,6 +1119,7 @@
         public String serviceInterface;
         public String secureSettingName;
         public String secondarySettingName;
+        public String managedServiceTypeTag;
         public String bindPermission;
         public String settingsAction;
         public int clientLabel;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5ee7ac4..e7bfa2d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -87,7 +87,6 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
@@ -110,7 +109,10 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -195,9 +197,9 @@
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /** {@hide} */
@@ -319,7 +321,6 @@
     final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
     final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
     final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
-    final PolicyAccess mPolicyAccess = new PolicyAccess();
 
     // The last key in this list owns the hardware.
     ArrayList<String> mLights = new ArrayList<>();
@@ -404,14 +405,58 @@
 
     }
 
+    protected void readDefaultApprovedServices() {
+        final int userId = UserHandle.USER_SYSTEM;
+        String defaultListenerAccess = getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultListenerAccessPackages);
+        if (defaultListenerAccess != null) {
+            for (String whitelisted :
+                    defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
+                // Gather all notification listener components for candidate pkgs.
+                Set<ComponentName> approvedListeners =
+                        mListeners.queryPackageForServices(whitelisted,
+                                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                for (ComponentName cn : approvedListeners) {
+                    try {
+                        getBinderService().setNotificationListenerAccessGrantedForUser(cn,
+                                    userId, true);
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+        String defaultDndAccess = getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultDndAccessPackages);
+        if (defaultListenerAccess != null) {
+            for (String whitelisted :
+                    defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
+                try {
+                    getBinderService().setNotificationPolicyAccessGranted(whitelisted, true);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
     private void readPolicyXml(InputStream stream, boolean forRestore)
             throws XmlPullParserException, NumberFormatException, IOException {
         final XmlPullParser parser = Xml.newPullParser();
         parser.setInput(stream, StandardCharsets.UTF_8.name());
 
+        boolean saveXml = false;
         while (parser.next() != END_DOCUMENT) {
             mZenModeHelper.readXml(parser, forRestore);
             mRankingHelper.readXml(parser, forRestore);
+            saveXml |= mListeners.readXml(parser);
+            saveXml |= mNotificationAssistants.readXml(parser);
+            saveXml |= mConditionProviders.readXml(parser);
+        }
+
+        if (saveXml) {
+            savePolicyFile();
         }
     }
 
@@ -425,6 +470,8 @@
                 readPolicyXml(infile, false /*forRestore*/);
             } catch (FileNotFoundException e) {
                 // No data yet
+                // Load default managed services approvals
+                readDefaultApprovedServices();
             } catch (IOException e) {
                 Log.wtf(TAG, "Unable to read notification policy", e);
             } catch (NumberFormatException e) {
@@ -472,6 +519,9 @@
         out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
         mZenModeHelper.writeXml(out, forBackup);
         mRankingHelper.writeXml(out, forBackup);
+        mListeners.writeXml(out, forBackup);
+        mNotificationAssistants.writeXml(out, forBackup);
+        mConditionProviders.writeXml(out, forBackup);
         out.endTag(null, TAG_NOTIFICATION_POLICY);
         out.endDocument();
     }
@@ -726,6 +776,22 @@
         updateLightsLocked();
     }
 
+    private final BroadcastReceiver mRestoreReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
+                try {
+                    String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+                    String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
+                    mListeners.onSettingRestored(element, newValue, getSendingUserId());
+                    mConditionProviders.onSettingRestored(element, newValue, getSendingUserId());
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Cannot restore managed services from settings", e);
+                }
+            }
+        }
+    };
+
     private final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -827,9 +893,9 @@
                         }
                     }
                 }
-                mListeners.onPackagesChanged(removingPackage, pkgList);
-                mNotificationAssistants.onPackagesChanged(removingPackage, pkgList);
-                mConditionProviders.onPackagesChanged(removingPackage, pkgList);
+                mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
+                mNotificationAssistants.onPackagesChanged(removingPackage, pkgList, uidList);
+                mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
                 mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList, uidList);
                 savePolicyFile();
             }
@@ -1058,8 +1124,9 @@
     @VisibleForTesting
     void init(Looper looper, IPackageManager packageManager, PackageManager packageManagerClient,
             LightsManager lightsManager, NotificationListeners notificationListeners,
+            NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
             ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
-            NotificationUsageStats usageStats) {
+            NotificationUsageStats usageStats, AtomicFile policyFile) {
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1089,7 +1156,7 @@
                 mRankingHandler,
                 mUsageStats,
                 extractorNames);
-        mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+        mConditionProviders = conditionProviders;
         mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
@@ -1145,16 +1212,14 @@
             }
         });
 
-        final File systemDir = new File(Environment.getDataDirectory(), "system");
-        mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
-
-        loadPolicyFile();
-
         // This is a ManagedServices object that keeps track of the listeners.
         mListeners = notificationListeners;
 
         // This is a MangedServices object that keeps track of the assistant.
-        mNotificationAssistants = new NotificationAssistants();
+        mNotificationAssistants = notificationAssistants;
+
+        mPolicyFile = policyFile;
+        loadPolicyFile();
 
         mStatusBar = getLocalService(StatusBarManagerInternal.class);
         if (mStatusBar != null) {
@@ -1222,6 +1287,9 @@
         timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
         getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
 
+        IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
+        getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
+
         mSettingsObserver = new SettingsObserver(mHandler);
 
         mArchive = new Archive(resources.getInteger(
@@ -1249,9 +1317,15 @@
             }
         }, mUserProfiles);
 
+        final File systemDir = new File(Environment.getDataDirectory(), "system");
+
         init(Looper.myLooper(), AppGlobals.getPackageManager(), getContext().getPackageManager(),
-                getLocalService(LightsManager.class), new NotificationListeners(),
-                null, snoozeHelper, new NotificationUsageStats(getContext()));
+                getLocalService(LightsManager.class),
+                new NotificationListeners(AppGlobals.getPackageManager()),
+                new NotificationAssistants(AppGlobals.getPackageManager()),
+                new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
+                null, snoozeHelper, new NotificationUsageStats(getContext()),
+                new AtomicFile(new File(systemDir, "notification_policy.xml")));
         publishBinderService(Context.NOTIFICATION_SERVICE, mService);
         publishLocalService(NotificationManagerInternal.class, mInternalService);
     }
@@ -1814,17 +1888,20 @@
             cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
                     UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
 
+            final String[] packages = new String[] {packageName};
+            final int[] uids = new int[] {uid};
+
             // Listener & assistant
-            mListeners.onPackagesChanged(true, new String[] {packageName});
-            mNotificationAssistants.onPackagesChanged(true, new String[] {packageName});
+            mListeners.onPackagesChanged(true, packages, uids);
+            mNotificationAssistants.onPackagesChanged(true, packages, uids);
 
             // Zen
-            mConditionProviders.onPackagesChanged(true, new String[] {packageName});
+            mConditionProviders.onPackagesChanged(true, packages, uids);
 
             // Reset notification preferences
             if (!fromApp) {
-                mRankingHelper.onPackagesChanged(true, UserHandle.getCallingUserId(),
-                        new String[]{packageName}, new int[]{uid});
+                mRankingHelper.onPackagesChanged(
+                        true, UserHandle.getCallingUserId(), packages, uids);
             }
 
             savePolicyFile();
@@ -2468,7 +2545,8 @@
             String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
             final int packageCount = packages.length;
             for (int i = 0; i < packageCount; i++) {
-                if (checkPolicyAccess(packages[i])) {
+                if (mConditionProviders.isPackageOrComponentAllowed(
+                        packages[i], UserHandle.getUserId(uid))) {
                     accessAllowed = true;
                 }
             }
@@ -2491,7 +2569,8 @@
         }
 
         private boolean checkPackagePolicyAccess(String pkg) {
-            return mPolicyAccess.isPackageGranted(pkg);
+            return mConditionProviders.isPackageOrComponentAllowed(
+                    pkg, getCallingUserHandle().getIdentifier());
         }
 
         private boolean checkPolicyAccess(String pkg) {
@@ -2602,29 +2681,19 @@
         }
 
         @Override
-        public String[] getPackagesRequestingNotificationPolicyAccess()
-                throws RemoteException {
-            enforceSystemOrSystemUI("request policy access packages");
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                return mPolicyAccess.getRequestingPackages();
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
         public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
                 throws RemoteException {
-            enforceSystemOrSystemUI("grant notification policy access");
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                synchronized (mNotificationLock) {
-                    mPolicyAccess.put(pkg, granted);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
+            checkCallerIsSystemOrShell();
+            mConditionProviders.setPackageOrComponentEnabled(
+                    pkg, getCallingUserHandle().getIdentifier(), true, granted);
+
+            getContext().sendBroadcastAsUser(new Intent(NotificationManager
+                    .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                    .setPackage(pkg)
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+                    getCallingUserHandle(), null);
+
+            savePolicyFile();
         }
 
         @Override
@@ -2650,6 +2719,93 @@
         }
 
         @Override
+        public List<String> getEnabledNotificationListenerPackages() {
+            checkCallerIsSystem();
+            return mListeners.getAllowedPackages(getCallingUserHandle().getIdentifier());
+        }
+
+        @Override
+        public List<ComponentName> getEnabledNotificationListeners(int userId) {
+            checkCallerIsSystem();
+            return mListeners.getAllowedComponents(userId);
+        }
+
+        @Override
+        public boolean isNotificationListenerAccessGranted(ComponentName listener) {
+            Preconditions.checkNotNull(listener);
+            checkCallerIsSystemOrSameApp(listener.getPackageName());
+            return mListeners.isPackageOrComponentAllowed(listener.flattenToString(),
+                    getCallingUserHandle().getIdentifier());
+        }
+
+        @Override
+        public boolean isNotificationListenerAccessGrantedForUser(ComponentName listener,
+                int userId) {
+            Preconditions.checkNotNull(listener);
+            checkCallerIsSystem();
+            return mListeners.isPackageOrComponentAllowed(listener.flattenToString(),
+                    userId);
+        }
+
+        @Override
+        public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
+            Preconditions.checkNotNull(assistant);
+            checkCallerIsSystemOrSameApp(assistant.getPackageName());
+            return mNotificationAssistants.isPackageOrComponentAllowed(assistant.flattenToString(),
+                    getCallingUserHandle().getIdentifier());
+        }
+
+        @Override
+        public void setNotificationListenerAccessGranted(ComponentName listener,
+                boolean granted) throws RemoteException {
+            setNotificationListenerAccessGrantedForUser(
+                    listener, getCallingUserHandle().getIdentifier(), granted);
+        }
+
+        @Override
+        public void setNotificationAssistantAccessGranted(ComponentName assistant,
+                boolean granted) throws RemoteException {
+            setNotificationAssistantAccessGrantedForUser(
+                    assistant, getCallingUserHandle().getIdentifier(), granted);
+        }
+
+        @Override
+        public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
+                boolean granted) throws RemoteException {
+            Preconditions.checkNotNull(listener);
+            enforceSystemOrSystemUI("grant notification listener access");
+            mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(),
+                    userId, false, granted);
+            mListeners.setPackageOrComponentEnabled(listener.flattenToString(),
+                    userId, true, granted);
+
+            getContext().sendBroadcastAsUser(new Intent(NotificationManager
+                    .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                    .setPackage(listener.getPackageName())
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), getCallingUserHandle(), null);
+
+            savePolicyFile();
+        }
+
+        @Override
+        public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant,
+                int userId, boolean granted) throws RemoteException {
+            Preconditions.checkNotNull(assistant);
+            enforceSystemOrSystemUI("grant notification assistant access");
+            mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
+                    userId, false, granted);
+            mNotificationAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
+                    userId, true, granted);
+
+            getContext().sendBroadcastAsUser(new Intent(NotificationManager
+                    .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                    .setPackage(assistant.getPackageName())
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), getCallingUserHandle(), null);
+
+            savePolicyFile();
+        }
+
+        @Override
         public void applyEnqueuedAdjustmentFromAssistant(INotificationListener token,
                 Adjustment adjustment) throws RemoteException {
             final long identity = Binder.clearCallingIdentity();
@@ -2765,6 +2921,13 @@
             }
             return uid;
         }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+                throws RemoteException {
+            new ShellCmd().exec(this, in, out, err, args, callback, resultReceiver);
+        }
     };
 
     private void applyAdjustment(NotificationRecord n, Adjustment adjustment) {
@@ -3074,9 +3237,6 @@
                 ZenLog.dump(pw, "    ");
             }
 
-            pw.println("\n  Policy access:");
-            pw.print("    mPolicyAccess: "); pw.println(mPolicyAccess);
-
             pw.println("\n  Condition providers:");
             mConditionProviders.dump(pw, filter);
 
@@ -4759,6 +4919,13 @@
         return isUidSystemOrPhone(Binder.getCallingUid());
     }
 
+    private void checkCallerIsSystemOrShell() {
+        if (Binder.getCallingUid() == Process.SHELL_UID) {
+            return;
+        }
+        checkCallerIsSystem();
+    }
+
     private void checkCallerIsSystem() {
         if (isCallerSystemOrPhone()) {
             return;
@@ -4964,9 +5131,10 @@
     }
 
     public class NotificationAssistants extends ManagedServices {
+        static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
 
-        public NotificationAssistants() {
-            super(getContext(), mHandler, mNotificationLock, mUserProfiles);
+        public NotificationAssistants(IPackageManager pm) {
+            super(getContext(), mNotificationLock, mUserProfiles, pm);
         }
 
         @Override
@@ -4974,6 +5142,7 @@
             Config c = new Config();
             c.caption = "notification assistant service";
             c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
+            c.managedServiceTypeTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
             c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
             c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
             c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
@@ -5071,11 +5240,13 @@
     }
 
     public class NotificationListeners extends ManagedServices {
+        static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
 
         private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
 
-        public NotificationListeners() {
-            super(getContext(), mHandler, mNotificationLock, mUserProfiles);
+        public NotificationListeners(IPackageManager pm) {
+            super(getContext(), mNotificationLock, mUserProfiles, pm);
+
         }
 
         @Override
@@ -5083,6 +5254,7 @@
             Config c = new Config();
             c.caption = "notification listener";
             c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
+            c.managedServiceTypeTag = TAG_ENABLED_NOTIFICATION_LISTENERS;
             c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
             c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
             c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
@@ -5475,78 +5647,39 @@
         }
     }
 
-    private final class PolicyAccess {
-        private static final String SEPARATOR = ":";
-        private final String[] PERM = {
-            android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
-        };
+    private class ShellCmd extends ShellCommand {
+        public static final String USAGE = "help\n"
+                + "allow_dnd PACKAGE\n"
+                + "disallow_dnd PACKAGE";
 
-        public boolean isPackageGranted(String pkg) {
-            return pkg != null && getGrantedPackages().contains(pkg);
-        }
-
-        public void put(String pkg, boolean granted) {
-            if (pkg == null) return;
-            final ArraySet<String> pkgs = getGrantedPackages();
-            boolean changed;
-            if (granted) {
-                changed = pkgs.add(pkg);
-            } else {
-                changed = pkgs.remove(pkg);
-            }
-            if (!changed) return;
-            final String setting = TextUtils.join(SEPARATOR, pkgs);
-            final int currentUser = ActivityManager.getCurrentUser();
-            Settings.Secure.putStringForUser(getContext().getContentResolver(),
-                    Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                    setting,
-                    currentUser);
-            getContext().sendBroadcastAsUser(new Intent(NotificationManager
-                    .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
-                .setPackage(pkg)
-                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), new UserHandle(currentUser), null);
-        }
-
-        public ArraySet<String> getGrantedPackages() {
-            final ArraySet<String> pkgs = new ArraySet<>();
-
-            long identity = Binder.clearCallingIdentity();
+        @Override
+        public int onCommand(String cmd) {
             try {
-                final String setting = Settings.Secure.getStringForUser(
-                        getContext().getContentResolver(),
-                        Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                        ActivityManager.getCurrentUser());
-                if (setting != null) {
-                    final String[] tokens = setting.split(SEPARATOR);
-                    for (int i = 0; i < tokens.length; i++) {
-                        String token = tokens[i];
-                        if (token != null) {
-                            token = token.trim();
-                        }
-                        if (TextUtils.isEmpty(token)) {
-                            continue;
-                        }
-                        pkgs.add(token);
+                switch (cmd) {
+                    case "allow_dnd": {
+                        getBinderService().setNotificationPolicyAccessGranted(
+                                getNextArgRequired(), true);
                     }
+                    break;
+
+                    case "disallow_dnd": {
+                        getBinderService().setNotificationPolicyAccessGranted(
+                                getNextArgRequired(), false);
+                    }
+                    break;
+
+                    default:
+                        return handleDefaultCommands(cmd);
                 }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error running shell command", e);
             }
-            return pkgs;
+            return 0;
         }
 
-        public String[] getRequestingPackages() throws RemoteException {
-            final ParceledListSlice list = mPackageManager
-                    .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
-                            ActivityManager.getCurrentUser());
-            final List<PackageInfo> pkgs = list.getList();
-            if (pkgs == null || pkgs.isEmpty()) return new String[0];
-            final int N = pkgs.size();
-            final String[] rt = new String[N];
-            for (int i = 0; i < N; i++) {
-                rt[i] = pkgs.get(i).packageName;
-            }
-            return rt;
+        @Override
+        public void onHelp() {
+            getOutPrintWriter().println(USAGE);
         }
     }
 }
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index b3c6ff6..bc617de 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -169,7 +169,7 @@
             }
             if (type == XmlPullParser.START_TAG) {
                 if (TAG_PACKAGE.equals(tag)) {
-                    int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
+                    int uid = XmlUtils.safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
                     String name = parser.getAttributeValue(null, ATT_NAME);
                     if (!TextUtils.isEmpty(name)) {
                         if (forRestore) {
@@ -182,14 +182,14 @@
                         }
 
                         Record r = getOrCreateRecord(name, uid,
-                                safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
-                                safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
-                                safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
-                                safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
-                        r.importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
-                        r.priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
-                        r.visibility = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
-                        r.showBadge = safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
+                                XmlUtils.safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
+                                XmlUtils.safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
+                                XmlUtils.safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+                                XmlUtils.safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
+                        r.importance = XmlUtils.safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+                        r.priority = XmlUtils.safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+                        r.visibility = XmlUtils.safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+                        r.showBadge = XmlUtils.safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
 
                         final int innerDepth = parser.getDepth();
                         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -214,8 +214,8 @@
                             if (TAG_CHANNEL.equals(tagName)) {
                                 String id = parser.getAttributeValue(null, ATT_ID);
                                 String channelName = parser.getAttributeValue(null, ATT_NAME);
-                                int channelImportance =
-                                        safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+                                int channelImportance = XmlUtils.safeInt(
+                                        parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
                                 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
                                     NotificationChannel channel = new NotificationChannel(id,
                                             channelName, channelImportance);
@@ -467,26 +467,6 @@
         return Collections.binarySearch(notificationList, target, mFinalComparator);
     }
 
-    private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
-        final String value = parser.getAttributeValue(null, att);
-        if (TextUtils.isEmpty(value)) return defValue;
-        return Boolean.parseBoolean(value);
-    }
-
-    private static int safeInt(XmlPullParser parser, String att, int defValue) {
-        final String val = parser.getAttributeValue(null, att);
-        return tryParseInt(val, defValue);
-    }
-
-    private static int tryParseInt(String value, int defValue) {
-        if (TextUtils.isEmpty(value)) return defValue;
-        try {
-            return Integer.parseInt(value);
-        } catch (NumberFormatException e) {
-            return defValue;
-        }
-    }
-
     /**
      * Gets importance.
      */
diff --git a/services/core/java/com/android/server/notification/XmlUtils.java b/services/core/java/com/android/server/notification/XmlUtils.java
new file mode 100644
index 0000000..831d039
--- /dev/null
+++ b/services/core/java/com/android/server/notification/XmlUtils.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2017, 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 android.annotation.NonNull;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+
+class XmlUtils {
+
+    static @NonNull String safeString(XmlPullParser parser, String att, String defValue) {
+        final String value = parser.getAttributeValue(null, att);
+        if (value == null) return defValue;
+        return value;
+    }
+
+    static @NonNull boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+        final String value = parser.getAttributeValue(null, att);
+        if (TextUtils.isEmpty(value)) return defValue;
+        return Boolean.parseBoolean(value);
+    }
+
+    static @NonNull int safeInt(XmlPullParser parser, String att, int defValue) {
+        final String val = parser.getAttributeValue(null, att);
+        return tryParseInt(val, defValue);
+    }
+
+    private static int tryParseInt(String value, int defValue) {
+        if (TextUtils.isEmpty(value)) return defValue;
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return defValue;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 55d4719..3c53771 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.INotificationManager;
 import android.app.Vr2dDisplayProperties;
 import android.app.NotificationManager;
 import android.annotation.NonNull;
@@ -42,6 +43,7 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -75,6 +77,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -142,6 +145,7 @@
     private VrState mPendingState;
     private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
     private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
+    private INotificationManager mNotificationManager;
     /** Tracks the state of the screen and keyguard UI.*/
     private int mSystemSleepFlags = FLAG_AWAKE;
     /**
@@ -593,6 +597,8 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            mNotificationManager = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
             synchronized (mLock) {
                 Looper looper = Looper.getMainLooper();
                 Handler handler = new Handler(looper);
@@ -836,50 +842,28 @@
     }
 
     private void grantNotificationListenerAccess(String pkg, int userId) {
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
         PackageManager pm = mContext.getPackageManager();
         ArraySet<ComponentName> possibleServices = EnabledComponentsObserver.loadComponentNames(pm,
                 userId, NotificationListenerService.SERVICE_INTERFACE,
                 android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
-        ContentResolver resolver = mContext.getContentResolver();
-
-        ArraySet<String> current = getNotificationListeners(resolver, userId);
 
         for (ComponentName c : possibleServices) {
-            String flatName = c.flattenToString();
-            if (Objects.equals(c.getPackageName(), pkg)
-                    && !current.contains(flatName)) {
-                current.add(flatName);
+            if (Objects.equals(c.getPackageName(), pkg)) {
+                nm.setNotificationListenerAccessGrantedForUser(c, userId, true);
             }
         }
-
-        if (current.size() > 0) {
-            String flatSettings = formatSettings(current);
-            Settings.Secure.putStringForUser(resolver,
-                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                    flatSettings, userId);
-        }
     }
 
     private void revokeNotificationListenerAccess(String pkg, int userId) {
-        ContentResolver resolver = mContext.getContentResolver();
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        List<ComponentName> current = nm.getEnabledNotificationListeners(userId);
 
-        ArraySet<String> current = getNotificationListeners(resolver, userId);
-
-        ArrayList<String> toRemove = new ArrayList<>();
-
-        for (String c : current) {
-            ComponentName component = ComponentName.unflattenFromString(c);
+        for (ComponentName component : current) {
             if (component != null && component.getPackageName().equals(pkg)) {
-                toRemove.add(c);
+                nm.setNotificationListenerAccessGrantedForUser(component, userId, false);
             }
         }
-
-        current.removeAll(toRemove);
-
-        String flatSettings = formatSettings(current);
-        Settings.Secure.putStringForUser(resolver,
-                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                flatSettings, userId);
     }
 
     private void grantCoarseLocationPermissionIfNeeded(String pkg, int userId) {
diff --git a/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java
new file mode 100644
index 0000000..ffbd8d4
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java
@@ -0,0 +1,700 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
+import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ManagedServicesTest extends NotificationTestCase {
+
+    @Mock
+    private IPackageManager mIpm;
+    @Mock
+    private PackageManager mPm;
+    @Mock
+    private UserManager mUm;
+    @Mock
+    private ManagedServices.UserProfiles mUserProfiles;
+    Object mLock = new Object();
+
+    UserInfo mZero = new UserInfo(0, "zero", 0);
+    UserInfo mTen = new UserInfo(10, "ten", 0);
+
+    private static final String SETTING = "setting";
+    private static final String SECONDARY_SETTING = "secondary_setting";
+
+    private ArrayMap<Integer, String> mExpectedPrimaryPackages;
+    private ArrayMap<Integer, String> mExpectedPrimaryComponentNames;
+    private ArrayMap<Integer, String> mExpectedSecondaryPackages;
+    private ArrayMap<Integer, String> mExpectedSecondaryComponentNames;
+
+    private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary = new ArrayMap<>();
+    private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary = new ArrayMap<>();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        getContext().setMockPackageManager(mPm);
+        getContext().addMockSystemService(Context.USER_SERVICE, mUm);
+
+        List<UserInfo> users = new ArrayList<>();
+        users.add(mZero);
+        users.add(mTen);
+        users.add(new UserInfo(11, "11", 0));
+        users.add(new UserInfo(12, "12", 0));
+        for (UserInfo user : users) {
+            when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
+        }
+        when(mUm.getUsers()).thenReturn(users);
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(new int[] {0, 10, 11, 12});
+
+        mExpectedPrimaryPackages = new ArrayMap<>();
+        mExpectedPrimaryPackages.put(0, "this.is.a.package.name:another.package");
+        mExpectedPrimaryPackages.put(10, "this.is.another.package");
+        mExpectedPrimaryPackages.put(11, "");
+        mExpectedPrimaryPackages.put(12, "bananas!");
+        mExpectedPrimaryComponentNames = new ArrayMap<>();
+        mExpectedPrimaryComponentNames.put(0, "this.is.a.package.name/Ba:another.package/B1");
+        mExpectedPrimaryComponentNames.put(10, "this.is.another.package/M1");
+        mExpectedPrimaryComponentNames.put(11, "");
+        mExpectedPrimaryComponentNames.put(12, "bananas!/Bananas!");
+        mExpectedPrimary.put(APPROVAL_BY_PACKAGE, mExpectedPrimaryPackages);
+        mExpectedPrimary.put(APPROVAL_BY_COMPONENT, mExpectedPrimaryComponentNames);
+
+        mExpectedSecondaryComponentNames = new ArrayMap<>();
+        mExpectedSecondaryComponentNames.put(0, "secondary/component.Name");
+        mExpectedSecondaryComponentNames.put(10,
+                "this.is.another.package/with.Component:component/2:package/component2");
+        mExpectedSecondaryPackages = new ArrayMap<>();
+        mExpectedSecondaryPackages.put(0, "secondary");
+        mExpectedSecondaryPackages.put(10,
+                "this.is.another.package:component:package");
+        mExpectedSecondary.put(APPROVAL_BY_PACKAGE, mExpectedSecondaryPackages);
+        mExpectedSecondary.put(APPROVAL_BY_COMPONENT, mExpectedSecondaryComponentNames);
+    }
+
+    @Test
+    public void testBackupAndRestore_migration() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+
+            for (int userId : mExpectedPrimary.get(approvalLevel).keySet()) {
+                service.onSettingRestored(
+                        service.getConfig().secureSettingName,
+                        mExpectedPrimary.get(approvalLevel).get(userId),
+                        userId);
+            }
+            verifyExpectedApprovedEntries(service, true);
+
+            for (int userId : mExpectedSecondary.get(approvalLevel).keySet()) {
+                service.onSettingRestored(service.getConfig().secondarySettingName,
+                        mExpectedSecondary.get(approvalLevel).get(userId), userId);
+            }
+            verifyExpectedApprovedEntries(service);
+        }
+    }
+
+    @Test
+    public void testReadXml_migrationFromSettings() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+
+            // approved services aren't in xml
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(new ByteArrayInputStream(new byte[]{})),
+                    null);
+            writeExpectedValuesToSettings(approvalLevel);
+
+            assertTrue(service.readXml(parser));
+
+            verifyExpectedApprovedEntries(service);
+        }
+    }
+
+    @Test
+    public void testReadXml() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+
+            assertFalse(loadXml(service));
+
+            verifyExpectedApprovedEntries(service);
+        }
+    }
+
+    @Test
+    public void testWriteXml_trimsMissingServices() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+            loadXml(service);
+
+            // remove missing
+            mExpectedPrimaryPackages.put(0, "another.package");
+            mExpectedPrimaryPackages.remove(12);
+            mExpectedPrimaryComponentNames.put(0, "another.package/B1");
+            mExpectedPrimaryComponentNames.remove(12);
+            mExpectedSecondaryPackages.put(10, "this.is.another.package:component");
+            mExpectedSecondaryComponentNames.put(
+                    10, "this.is.another.package/with.Component:component/2");
+
+            for (UserInfo userInfo : mUm.getUsers()) {
+                List<String> entriesExpectedToHaveServices = new ArrayList<>();
+                if (mExpectedPrimary.get(approvalLevel).containsKey(userInfo.id)) {
+                    for (String packageOrComponent :
+                            mExpectedPrimary.get(approvalLevel).get(userInfo.id).split(":")) {
+                        if (!TextUtils.isEmpty(packageOrComponent)) {
+                            entriesExpectedToHaveServices.add(
+                                    service.getPackageName(packageOrComponent));
+                        }
+                    }
+                }
+                if (mExpectedSecondary.get(approvalLevel).containsKey(userInfo.id)) {
+                    for (String packageOrComponent :
+                            mExpectedSecondary.get(approvalLevel).get(userInfo.id).split(":")) {
+                        if (!TextUtils.isEmpty(packageOrComponent)) {
+                            entriesExpectedToHaveServices.add(
+                                    service.getPackageName(packageOrComponent));
+                        }
+                    }
+                }
+                addExpectedServices(service, entriesExpectedToHaveServices, userInfo.id);
+            }
+
+            XmlSerializer serializer = new FastXmlSerializer();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+            serializer.startDocument(null, true);
+            service.writeXml(serializer, true);
+            serializer.endDocument();
+            serializer.flush();
+
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(
+                    new ByteArrayInputStream(baos.toByteArray())), null);
+            parser.nextTag();
+            assertFalse(service.readXml(parser));
+
+            verifyExpectedApprovedEntries(service);
+            assertFalse(service.isPackageOrComponentAllowed("this.is.a.package.name", 0));
+            assertFalse(service.isPackageOrComponentAllowed("bananas!", 12));
+            assertFalse(service.isPackageOrComponentAllowed("package/component2", 10));
+        }
+    }
+
+    @Test
+    public void testWriteXml_writesSetting() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+            loadXml(service);
+
+            XmlSerializer serializer = new FastXmlSerializer();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+            serializer.startDocument(null, true);
+            service.writeXml(serializer, false);
+            serializer.endDocument();
+            serializer.flush();
+
+            for (int userId : mUserProfiles.getCurrentProfileIds()) {
+                List<String> expected =
+                        stringToList(mExpectedPrimary.get(approvalLevel).get(userId));
+                List<String> actual = stringToList(Settings.Secure.getStringForUser(
+                        getContext().getContentResolver(),
+                        service.getConfig().secureSettingName, userId));
+                assertContentsInAnyOrder(actual, expected);
+            }
+        }
+    }
+
+    @Test
+    public void rebindServices_onlyBindsExactMatchesIfComponent() throws Exception {
+        // If the primary and secondary lists contain component names, only those components within
+        // the package should be matched
+        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                mIpm,
+                ManagedServices.APPROVAL_BY_COMPONENT);
+
+        List<String> packages = new ArrayList<>();
+        packages.add("package");
+        packages.add("anotherPackage");
+        addExpectedServices(service, packages, 0);
+
+        // only 2 components are approved per package
+        mExpectedPrimaryComponentNames.clear();
+        mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+        mExpectedSecondaryComponentNames.clear();
+        mExpectedSecondaryComponentNames.put(0, "anotherPackage/C1:anotherPackage/C2");
+
+        loadXml(service);
+
+        // verify the 2 components per package are enabled (bound)
+        verifyExpectedBoundEntries(service, true);
+        verifyExpectedBoundEntries(service, false);
+
+        // verify the last component per package is not enabled/we don't try to bind to it
+        for (String pkg : packages) {
+            ComponentName unapprovedAdditionalComponent =
+                    ComponentName.unflattenFromString(pkg + "/C3");
+            assertFalse(
+                    service.isComponentEnabledForCurrentProfiles(
+                            unapprovedAdditionalComponent));
+            verify(mIpm, never()).getServiceInfo(
+                    eq(unapprovedAdditionalComponent), anyInt(), anyInt());
+        }
+    }
+
+    @Test
+    public void rebindServices_bindsEverythingInAPackage() throws Exception {
+        // If the primary and secondary lists contain packages, all components within those packages
+        // should be bound
+        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_PACKAGE);
+
+        List<String> packages = new ArrayList<>();
+        packages.add("package");
+        packages.add("packagea");
+        addExpectedServices(service, packages, 0);
+
+        // 2 approved packages
+        mExpectedPrimaryPackages.clear();
+        mExpectedPrimaryPackages.put(0, "package");
+        mExpectedSecondaryPackages.clear();
+        mExpectedSecondaryPackages.put(0, "packagea");
+
+        loadXml(service);
+
+        // verify the 3 components per package are enabled (bound)
+        verifyExpectedBoundEntries(service, true);
+        verifyExpectedBoundEntries(service, false);
+    }
+
+    @Test
+    public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(new ByteArrayInputStream(new byte[]{})),
+                    null);
+            writeExpectedValuesToSettings(approvalLevel);
+            service.readXml(parser);
+
+            mExpectedPrimaryPackages.put(0, "another.package");
+            mExpectedPrimaryComponentNames.put(0, "another.package/B1");
+            service.onPackagesChanged(true, new String[]{"this.is.a.package.name"}, new int[]{103});
+
+            verifyExpectedApprovedEntries(service);
+        }
+    }
+
+    @Test
+    public void testPackageUninstall_componentNoLongerInApprovedList() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(new ByteArrayInputStream(new byte[]{})),
+                    null);
+            writeExpectedValuesToSettings(approvalLevel);
+            service.readXml(parser);
+
+            mExpectedSecondaryComponentNames.put(10, "component/2");
+            mExpectedSecondaryPackages.put(10, "component");
+            service.onPackagesChanged(true, new String[]{"this.is.another.package"}, new int[]{
+                    UserHandle.PER_USER_RANGE + 1});
+
+            verifyExpectedApprovedEntries(service);
+        }
+    }
+
+    @Test
+    public void testSetPackageOrComponentEnabled() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+            ArrayMap<Integer, ArrayList<String>> expectedEnabled = new ArrayMap<>();
+            expectedEnabled.put(0,
+                    Lists.newArrayList(new String[]{"package/Comp", "package/C2", "again/M4"}));
+            expectedEnabled.put(10,
+                    Lists.newArrayList(new String[]{"user10package/B", "user10/Component",
+                            "user10package1/K", "user10.3/Component", "user10package2/L",
+                            "user10.4/Component"}));
+
+            for (int userId : expectedEnabled.keySet()) {
+                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+                for (int i = 0; i < expectedForUser.size(); i++) {
+                    boolean primary = i % 2 == 0;
+                    service.setPackageOrComponentEnabled(expectedForUser.get(i), userId, primary,
+                            true);
+                }
+            }
+
+            // verify everything added is approved
+            for (int userId : expectedEnabled.keySet()) {
+                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+                for (int i = 0; i < expectedForUser.size(); i++) {
+                    String verifyValue  = (approvalLevel == APPROVAL_BY_COMPONENT)
+                            ? expectedForUser.get(i)
+                            : service.getPackageName(expectedForUser.get(i));
+                    assertTrue("Not allowed: user: " + userId + " entry: " + verifyValue
+                            + " for approval level " + approvalLevel,
+                            service.isPackageOrComponentAllowed(verifyValue, userId));
+                }
+            }
+
+            ArrayMap<Integer, ArrayList<String>> expectedNoAccess = new ArrayMap<>();
+            for (int userId : expectedEnabled.keySet()) {
+                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+                for (int i = expectedForUser.size() - 1; i >= 0; i--) {
+                    ArrayList<String> removed = new ArrayList<>();
+                    if (i % 3 == 0) {
+                        String revokeAccessFor = expectedForUser.remove(i);
+                        removed.add(revokeAccessFor);
+                        service.setPackageOrComponentEnabled(
+                                revokeAccessFor, userId, i % 2 == 0, false);
+                    }
+                    expectedNoAccess.put(userId, removed);
+                }
+            }
+
+            // verify everything still there is approved
+            for (int userId : expectedEnabled.keySet()) {
+                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+                for (int i = 0; i < expectedForUser.size(); i++) {
+                    String verifyValue  = (approvalLevel == APPROVAL_BY_COMPONENT)
+                            ? expectedForUser.get(i)
+                            : service.getPackageName(expectedForUser.get(i));
+                    assertTrue("Not allowed: user: " + userId + " entry: " + verifyValue,
+                            service.isPackageOrComponentAllowed(verifyValue, userId));
+                }
+            }
+            // verify everything removed isn't
+            for (int userId : expectedNoAccess.keySet()) {
+                ArrayList<String> notExpectedForUser = expectedNoAccess.get(userId);
+                for (int i = 0; i < notExpectedForUser.size(); i++) {
+                    assertFalse(
+                            "Is allowed: user: " + userId + " entry: " + notExpectedForUser.get(i),
+                            service.isPackageOrComponentAllowed(notExpectedForUser.get(i), userId));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testGetAllowedPackages() throws Exception {
+        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+                    mIpm, approvalLevel);
+            loadXml(service);
+
+            List<String> allowedPackagesForUser0 = new ArrayList<>();
+            allowedPackagesForUser0.add("this.is.a.package.name");
+            allowedPackagesForUser0.add("another.package");
+            allowedPackagesForUser0.add("secondary");
+
+            List<String> actual = service.getAllowedPackages(0);
+            assertEquals(3, actual.size());
+            for (String pkg : allowedPackagesForUser0) {
+                assertTrue(actual.contains(pkg));
+            }
+
+            List<String> allowedPackagesForUser10 = new ArrayList<>();
+            allowedPackagesForUser10.add("this.is.another.package");
+            allowedPackagesForUser10.add("package");
+            allowedPackagesForUser10.add("this.is.another.package");
+            allowedPackagesForUser10.add("component");
+
+            actual = service.getAllowedPackages(10);
+            assertEquals(4, actual.size());
+            for (String pkg : allowedPackagesForUser10) {
+                assertTrue(actual.contains(pkg));
+            }
+        }
+    }
+
+    @Test
+    public void testGetAllowedComponents() throws Exception {
+        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_COMPONENT);
+        loadXml(service);
+
+        List<ComponentName> expected = new ArrayList<>();
+        expected.add(ComponentName.unflattenFromString("this.is.another.package/M1"));
+        expected.add(ComponentName.unflattenFromString("this.is.another.package/with.Component"));
+        expected.add(ComponentName.unflattenFromString("component/2"));
+        expected.add(ComponentName.unflattenFromString("package/component2"));
+
+        List<ComponentName> actual = service.getAllowedComponents(10);
+
+        assertContentsInAnyOrder(expected, actual);
+
+        assertEquals(expected.size(), actual.size());
+
+        for (ComponentName cn : expected) {
+            assertTrue("Actual missing " + cn, actual.contains(cn));
+        }
+
+        for (ComponentName cn : actual) {
+            assertTrue("Actual contains extra " + cn, expected.contains(cn));
+        }
+    }
+
+    @Test
+    public void testGetAllowedComponents_approvalByPackage() throws Exception {
+        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_PACKAGE);
+        loadXml(service);
+
+        assertEquals(0, service.getAllowedComponents(10).size());
+    }
+
+    private boolean loadXml(ManagedServices service) throws Exception {
+        final StringBuffer xml = new StringBuffer();
+        xml.append("<" + service.getConfig().managedServiceTypeTag + ">\n");
+        for (int userId : mExpectedPrimary.get(service.mApprovalLevel).keySet()) {
+            xml.append(getXmlEntry(
+                    mExpectedPrimary.get(service.mApprovalLevel).get(userId), userId, true));
+        }
+        for (int userId : mExpectedSecondary.get(service.mApprovalLevel).keySet()) {
+            xml.append(getXmlEntry(
+                    mExpectedSecondary.get(service.mApprovalLevel).get(userId), userId, false));
+        }
+        xml.append("</" + service.getConfig().managedServiceTypeTag + ">");
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        parser.nextTag();
+        return service.readXml(parser);
+    }
+
+    private void addExpectedServices(final ManagedServices service, final List<String> packages,
+            int userId) {
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), eq(userId))).
+                thenAnswer(new Answer<List<ResolveInfo>>() {
+                    @Override
+                    public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+                            throws Throwable {
+                        Object[] args = invocationOnMock.getArguments();
+                        Intent invocationIntent = (Intent) args[0];
+                        if (invocationIntent != null) {
+                            if (invocationIntent.getAction().equals(
+                                    service.getConfig().serviceInterface)
+                                    && packages.contains(invocationIntent.getPackage())) {
+                                List<ResolveInfo> dummyServices = new ArrayList<>();
+                                for (int i = 1; i <= 3; i ++) {
+                                    ResolveInfo resolveInfo = new ResolveInfo();
+                                    ServiceInfo serviceInfo = new ServiceInfo();
+                                    serviceInfo.packageName = invocationIntent.getPackage();
+                                    serviceInfo.name = "C"+i;
+                                    serviceInfo.permission = service.getConfig().bindPermission;
+                                    resolveInfo.serviceInfo = serviceInfo;
+                                    dummyServices.add(resolveInfo);
+                                }
+                                return dummyServices;
+                            }
+                        }
+                        return new ArrayList<>();
+                    }
+                });
+    }
+
+    private List<String> stringToList(String list) {
+        if (list == null) {
+            list = "";
+        }
+        return new ArrayList<>(Lists.newArrayList(list.split(
+                ManagedServices.ENABLED_SERVICES_SEPARATOR)));
+    }
+
+    private void assertContentsInAnyOrder(List<?> expected, List<?> actual) {
+        assertEquals(expected.size(), actual.size());
+
+        for (Object o : expected) {
+            assertTrue("Actual missing " + o, actual.contains(o));
+        }
+
+        for (Object o : actual) {
+            assertTrue("Actual contains extra " + o, expected.contains(o));
+        }
+    }
+
+    private void verifyExpectedBoundEntries(ManagedServices service, boolean primary)
+            throws Exception {
+        ArrayMap<Integer, String> verifyMap = primary ? mExpectedPrimary.get(service.mApprovalLevel)
+                : mExpectedSecondary.get(service.mApprovalLevel);
+        for (int userId : verifyMap.keySet()) {
+            for (String packageOrComponent : verifyMap.get(userId).split(":")) {
+                if (!TextUtils.isEmpty(packageOrComponent)) {
+                    if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) {
+                        assertTrue(packageOrComponent, service.isComponentEnabledForPackage(packageOrComponent));
+                        for (int i = 1; i <= 3; i ++) {
+                            ComponentName componentName = ComponentName.unflattenFromString(
+                                    packageOrComponent +"/C" + i);
+                            assertTrue(service.isComponentEnabledForCurrentProfiles(
+                                    componentName));
+                            verify(mIpm, times(1)).getServiceInfo(
+                                    eq(componentName), anyInt(), anyInt());
+                        }
+                    } else {
+                        ComponentName componentName =
+                                ComponentName.unflattenFromString(packageOrComponent);
+                        assertTrue(service.isComponentEnabledForCurrentProfiles(componentName));
+                        verify(mIpm, times(1)).getServiceInfo(
+                                eq(componentName), anyInt(), anyInt());
+                    }
+                }
+            }
+        }
+    }
+
+    private void verifyExpectedApprovedEntries(ManagedServices service) {
+        verifyExpectedApprovedEntries(service, true);
+        verifyExpectedApprovedEntries(service, false);
+    }
+
+    private void verifyExpectedApprovedEntries(ManagedServices service, boolean primary) {
+        ArrayMap<Integer, String> verifyMap = primary
+                ? mExpectedPrimary.get(service.mApprovalLevel)
+                : mExpectedSecondary.get(service.mApprovalLevel);
+        for (int userId : verifyMap.keySet()) {
+            for (String verifyValue : verifyMap.get(userId).split(":")) {
+                if (!TextUtils.isEmpty(verifyValue)) {
+                    assertTrue("service type " + service.mApprovalLevel + ":"
+                            + verifyValue + " is not allowed for user " + userId,
+                            service.isPackageOrComponentAllowed(verifyValue, userId));
+                }
+            }
+        }
+    }
+
+    private boolean isPackage(String packageOrComponent) {
+        final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
+        if (component != null) {
+            return false;
+        }
+        return true;
+    }
+
+    private void writeExpectedValuesToSettings(int approvalLevel) {
+        for (int userId : mExpectedPrimary.get(approvalLevel).keySet()) {
+            Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING,
+                    mExpectedPrimary.get(approvalLevel).get(userId), userId);
+        }
+        for (int userId : mExpectedSecondary.get(approvalLevel).keySet()) {
+            Settings.Secure.putStringForUser(getContext().getContentResolver(), SECONDARY_SETTING,
+                    mExpectedSecondary.get(approvalLevel).get(userId), userId);
+        }
+    }
+
+    private String getXmlEntry(String approved, int userId, boolean isPrimary) {
+        return "<" + ManagedServices.TAG_MANAGED_SERVICES + " "
+                + ManagedServices.ATT_USER_ID + "=\"" + userId +"\" "
+                + ManagedServices.ATT_IS_PRIMARY + "=\"" + isPrimary +"\" "
+                + ManagedServices.ATT_APPROVED_LIST + "=\"" + approved +"\" "
+                + "/>\n";
+    }
+
+    class TestManagedServices extends ManagedServices {
+
+        public TestManagedServices(Context context, Object mutex, UserProfiles userProfiles,
+                IPackageManager pm, int approvedServiceType) {
+            super(context, mutex, userProfiles, pm);
+            mApprovalLevel = approvedServiceType;
+        }
+
+        @Override
+        protected Config getConfig() {
+            final Config c = new Config();
+            c.managedServiceTypeTag= "test";
+            c.secureSettingName = SETTING;
+            c.secondarySettingName = SECONDARY_SETTING;
+            c.bindPermission = "permission";
+            c.serviceInterface = "serviceInterface";
+            return c;
+        }
+
+        @Override
+        protected IInterface asInterface(IBinder binder) {
+            return null;
+        }
+
+        @Override
+        protected boolean checkType(IInterface service) {
+            return false;
+        }
+
+        @Override
+        protected void onServiceAdded(ManagedServiceInfo info) {
+
+        }
+    }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6090e35..a356ae0 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -60,11 +60,16 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.AtomicFile;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -94,11 +99,16 @@
     @Mock
     private RankingHelper mRankingHelper;
     @Mock
+    AtomicFile mPolicyFile;
+    File mFile;
+    @Mock
     private NotificationUsageStats mUsageStats;
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
     @Mock
     private NotificationManagerService.NotificationListeners mNotificationListeners;
+    @Mock private NotificationManagerService.NotificationAssistants mNotificationAssistants;
+    @Mock private ConditionProviders mConditionProviders;
     private ManagedServices.ManagedServiceInfo mListener;
     @Mock private ICompanionDeviceManager mCompanionMgr;
     @Mock SnoozeHelper mSnoozeHelper;
@@ -146,12 +156,24 @@
         // Use this testable looper.
         mTestableLooper = TestableLooper.get(this);
 
+        mFile = new File(mContext.getCacheDir(), "test.xml");
+        mFile.createNewFile();
+        when(mPolicyFile.openRead()).thenReturn(new FileInputStream(mFile));
+        when(mPolicyFile.startWrite()).thenReturn(new FileOutputStream(mFile));
+
         mListener = mNotificationListeners.new ManagedServiceInfo(
                 null, new ComponentName(PKG, "test_class"), uid, true, null, 0);
         when(mNotificationListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
-        mNotificationManagerService.init(mTestableLooper.getLooper(), mPackageManager,
-                mPackageManagerClient, mockLightsManager, mNotificationListeners, mCompanionMgr,
-                mSnoozeHelper, mUsageStats);
+        try {
+            mNotificationManagerService.init(mTestableLooper.getLooper(), mPackageManager,
+                    mPackageManagerClient, mockLightsManager, mNotificationListeners,
+                    mNotificationAssistants, mConditionProviders, mCompanionMgr,
+                    mSnoozeHelper, mUsageStats, mPolicyFile);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
 
         // Tests call directly into the Binder.
         mBinderService = mNotificationManagerService.getBinderService();
@@ -161,6 +183,11 @@
                 PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mFile.delete();
+    }
+
     public void waitForIdle() throws Exception {
         mTestableLooper.processAllMessages();
     }
@@ -951,4 +978,61 @@
 
         verify(mSnoozeHelper, never()).repostGroupSummary(anyString(), anyInt(), anyString());
     }
+
+    @Test
+    public void testSetListenerAccess() throws Exception {
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationListenerAccessGranted(c, true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mNotificationListeners, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), 0, true, true);
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), 0, false, true);
+        verify(mNotificationAssistants, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testSetAssistantAccess() throws Exception {
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationAssistantAccessGranted(c, true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mNotificationAssistants, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), 0, true, true);
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), 0, false, true);
+        verify(mNotificationListeners, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testSetDndAccess() throws Exception {
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.getPackageName(), 0, true, true);
+        verify(mNotificationAssistants, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mNotificationListeners, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java b/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java
index cc30aab..1ee3412 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java
@@ -27,7 +27,7 @@
     public final TestableContext mContext =
             new TestableContext(InstrumentationRegistry.getContext(), null);
 
-    protected Context getContext() {
+    protected TestableContext getContext() {
         return mContext;
     }
 }
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index fe97bca1..5f2a224 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -36,7 +36,7 @@
 public class TestableSettingsProvider extends MockContentProvider {
 
     private static final String TAG = "TestableSettingsProvider";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
     private static final String MY_UNIQUE_KEY = "Key_" + TestableSettingsProvider.class.getName();
     private static TestableSettingsProvider sInstance;