Add managed profile whitelist to control NotificationListenerServices

Only let notification listeners installed in the primary profile
see work profile notification if allowed by policy

Bug: 36657192
Test: runtest systemui-notification
Test: runtest -c com.android.server.devicepolicy.DevicePolicyManagerTest    frameworks-services
Change-Id: If719151644380e9162180a24d12f798e42867c0a
diff --git a/api/current.txt b/api/current.txt
index 9a7d0a6..ecd4b37 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6281,6 +6281,7 @@
     method public int getPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String);
     method public int getPermissionPolicy(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
+    method public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
     method public long getRequiredStrongAuthTimeout(android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(android.content.ComponentName);
@@ -6365,6 +6366,7 @@
     method public boolean setPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String, int);
     method public void setPermissionPolicy(android.content.ComponentName, int);
     method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
+    method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 0b8017d..a471ea5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6490,6 +6490,7 @@
     method public int getPermissionPolicy(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
+    method public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
@@ -6586,6 +6587,7 @@
     method public boolean setPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String, int);
     method public void setPermissionPolicy(android.content.ComponentName, int);
     method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
+    method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 10cf5ce..bc156d4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6305,6 +6305,7 @@
     method public int getPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String);
     method public int getPermissionPolicy(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
+    method public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
     method public long getRequiredStrongAuthTimeout(android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(android.content.ComponentName);
@@ -6391,6 +6392,7 @@
     method public boolean setPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String, int);
     method public void setPermissionPolicy(android.content.ComponentName, int);
     method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
+    method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6d8d5e9..c9867e7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2743,8 +2743,8 @@
 
     /**
      * Called by a profile or device owner to provision a token which can later be used to reset the
-     * device lockscreen password (if called by device owner), or work challenge (if called by
-     * profile owner), via {@link #resetPasswordWithToken}.
+     * device lockscreen password (if called by device owner), or managed profile challenge (if
+     * called by profile owner), via {@link #resetPasswordWithToken}.
      * <p>
      * If the user currently has a lockscreen password, the provisioned token will not be
      * immediately usable; it only becomes active after the user performs a confirm credential
@@ -2820,8 +2820,8 @@
     }
 
     /**
-     * Called by device or profile owner to force set a new device unlock password or a work profile
-     * challenge on current user. This takes effect immediately.
+     * Called by device or profile owner to force set a new device unlock password or a managed
+     * profile challenge on current user. This takes effect immediately.
      * <p>
      * Unlike {@link #resetPassword}, this API can change the password even before the user or
      * device is unlocked or decrypted. The supplied token must have been previously provisioned via
@@ -5538,7 +5538,7 @@
      * Calling with a null value for the list disables the restriction so that all services can be
      * used, calling with an empty list only allows the builtin system's services.
      * <p>
-     * System accesibility services are always available to the user the list can't modify this.
+     * System accessibility services are always available to the user the list can't modify this.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param packageNames List of accessibility service package names.
@@ -5648,7 +5648,8 @@
      *         non-system input methods currently enabled that are not in the packageNames list.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
-    public boolean setPermittedInputMethods(@NonNull ComponentName admin, List<String> packageNames) {
+    public boolean setPermittedInputMethods(
+            @NonNull ComponentName admin, List<String> packageNames) {
         throwIfParentInstance("setPermittedInputMethods");
         if (mService != null) {
             try {
@@ -5730,6 +5731,85 @@
     }
 
     /**
+     * Called by a profile owner of a managed profile to set the packages that are allowed to use
+     * a {@link android.service.notification.NotificationListenerService} in the primary user to
+     * see notifications from the managed profile. By default all packages are permitted by this
+     * policy. When zero or more packages have been added, notification listeners installed on the
+     * primary user that are not in the list and are not part of the system won't receive events
+     * for managed profile notifications.
+     * <p>
+     * Calling with a {@code null} value for the list disables the restriction so that all
+     * notification listener services be used. Calling with an empty list disables all but the
+     * system's own notification listeners. System notification listener services are always
+     * available to the user.
+     * <p>
+     * If a device or profile owner want to stop notification listeners in their user from seeing
+     * that user's notifications they should prevent that service from running instead (e.g. via
+     * {@link #setApplicationHidden(ComponentName, String, boolean)})
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param packageList List of package names to whitelist
+     * @return true if setting the restriction succeeded. It will fail if called outside a managed
+     * profile
+     * @throws SecurityException if {@code admin} is not a profile owner.
+     *
+     * @see android.service.notification.NotificationListenerService
+     */
+    public boolean setPermittedCrossProfileNotificationListeners(
+            @NonNull ComponentName admin, @Nullable List<String> packageList) {
+        throwIfParentInstance("setPermittedCrossProfileNotificationListeners");
+        if (mService != null) {
+            try {
+                return mService.setPermittedCrossProfileNotificationListeners(admin, packageList);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the list of packages installed on the primary user that allowed to use a
+     * {@link android.service.notification.NotificationListenerService} to receive
+     * notifications from this managed profile, as set by the profile owner.
+     * <p>
+     * An empty list means no notification listener services except system ones are allowed.
+     * A {@code null} return value indicates that all notification listeners are allowed.
+     */
+    public @Nullable List<String> getPermittedCrossProfileNotificationListeners(
+            @NonNull ComponentName admin) {
+        throwIfParentInstance("getPermittedCrossProfileNotificationListeners");
+        if (mService != null) {
+            try {
+                return mService.getPermittedCrossProfileNotificationListeners(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if {@code NotificationListenerServices} from the given package are allowed to
+     * receive events for notifications from the given user id. Can only be called by the system uid
+     *
+     * @see #setPermittedCrossProfileNotificationListeners(ComponentName, List)
+     *
+     * @hide
+     */
+    public boolean isNotificationListenerServicePermitted(
+            @NonNull String packageName, @UserIdInt int userId) {
+        if (mService != null) {
+            try {
+                return mService.isNotificationListenerServicePermitted(packageName, userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
      * Get the list of apps to keep around as APKs even if no user has currently installed it. This
      * function can be called by a device owner or by a delegate given the
      * {@link #DELEGATION_KEEP_UNINSTALLED_PACKAGES} scope via {@link #setDelegatedScopes}.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8ea911f..e361d81 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -202,6 +202,10 @@
     List getPermittedInputMethodsForCurrentUser();
     boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
 
+    boolean setPermittedCrossProfileNotificationListeners(in ComponentName admin, in List<String> packageList);
+    List<String> getPermittedCrossProfileNotificationListeners(in ComponentName admin);
+    boolean isNotificationListenerServicePermitted(in String packageName, int userId);
+
     Intent createAdminSupportIntent(in String restriction);
     boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden);
     boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0774779..718f12c 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -19,10 +19,12 @@
 import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.DEVICE_POLICY_SERVICE;
 
 import android.annotation.NonNull;
 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;
@@ -39,6 +41,7 @@
 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;
@@ -889,7 +892,9 @@
             if (this.userid == UserHandle.USER_ALL) return true;
             if (this.isSystem) return true;
             if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
-            return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
+            return supportsProfiles()
+                    && mUserProfiles.isCurrentProfile(nid)
+                    && isPermittedForProfile(nid);
         }
 
         public boolean supportsProfiles() {
@@ -900,7 +905,7 @@
         public void binderDied() {
             if (DEBUG) Slog.d(TAG, "binderDied");
             // Remove the service, but don't unbind from the service. The system will bring the
-            // service back up, and the onServiceConnected handler will readd the service with the
+            // service back up, and the onServiceConnected handler will read the service with the
             // new binding. If this isn't a bound service, and is just a registered
             // service, just removing it from the list is all we need to do anyway.
             removeServiceImpl(this.service, this.userid);
@@ -912,6 +917,26 @@
             if (this.connection == null) return false;
             return mEnabledServicesForCurrentProfiles.contains(this.component);
         }
+
+        /**
+         * Returns true if this service is allowed to receive events for the given userId. A
+         * managed profile owner can disallow non-system services running outside of the profile
+         * from receiving events from the profile.
+         */
+        public boolean isPermittedForProfile(int userId) {
+            if (!mUserProfiles.isManagedProfile(userId)) {
+                return true;
+            }
+            DevicePolicyManager dpm =
+                    (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return dpm.isNotificationListenerServicePermitted(
+                        component.getPackageName(), userId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     }
 
     /** convenience method for looking in mEnabledServicesForCurrentProfiles */
@@ -953,6 +978,13 @@
                 return mCurrentProfiles.get(userId) != null;
             }
         }
+
+        public boolean isManagedProfile(int userId) {
+            synchronized (mCurrentProfiles) {
+                UserInfo user = mCurrentProfiles.get(userId);
+                return user != null && user.isManagedProfile();
+            }
+        }
     }
 
     public static class Config {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ab86966..6063ef7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -654,6 +654,8 @@
         private static final String TAG_GLOBAL_PROXY_SPEC = "global-proxy-spec";
         private static final String TAG_SPECIFIES_GLOBAL_PROXY = "specifies-global-proxy";
         private static final String TAG_PERMITTED_IMES = "permitted-imes";
+        private static final String TAG_PERMITTED_NOTIFICATION_LISTENERS =
+                "permitted-notification-listeners";
         private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
         private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
         private static final String TAG_STRONG_AUTH_UNLOCK_TIMEOUT = "strong-auth-unlock-timeout";
@@ -761,6 +763,11 @@
         // allowed.
         List<String> permittedInputMethods;
 
+        // The list of packages allowed to use a NotificationListenerService to receive events for
+        // notifications from this user. Null means that all packages are allowed. Empty list means
+        // that only packages from the system are allowed.
+        List<String> permittedNotificationListeners;
+
         // List of package names to keep cached.
         List<String> keepUninstalledPackages;
 
@@ -1007,6 +1014,8 @@
             writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
                     permittedAccessiblityServices);
             writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+            writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
+                    permittedNotificationListeners);
             writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
             if (hasUserRestrictions()) {
                 UserRestrictionsUtils.writeRestrictions(
@@ -1178,6 +1187,8 @@
                     permittedAccessiblityServices = readPackageList(parser, tag);
                 } else if (TAG_PERMITTED_IMES.equals(tag)) {
                     permittedInputMethods = readPackageList(parser, tag);
+                } else if (TAG_PERMITTED_NOTIFICATION_LISTENERS.equals(tag)) {
+                    permittedNotificationListeners = readPackageList(parser, tag);
                 } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
                     keepUninstalledPackages = readPackageList(parser, tag);
                 } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
@@ -1398,6 +1409,10 @@
                 pw.print(prefix); pw.print("permittedInputMethods=");
                     pw.println(permittedInputMethods);
             }
+            if (permittedNotificationListeners != null) {
+                pw.print(prefix); pw.print("permittedNotificationListeners=");
+                pw.println(permittedNotificationListeners);
+            }
             if (keepUninstalledPackages != null) {
                 pw.print(prefix); pw.print("keepUninstalledPackages=");
                     pw.println(keepUninstalledPackages);
@@ -7749,14 +7764,14 @@
             if (admin.permittedAccessiblityServices == null) {
                 return true;
             }
-            return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+            return checkPackagesInPermittedListOrSystem(Collections.singletonList(packageName),
                     admin.permittedAccessiblityServices, userHandle);
         }
     }
 
     private boolean checkCallerIsCurrentUserOrProfile() {
-        int callingUserId = UserHandle.getCallingUserId();
-        long token = mInjector.binderClearCallingIdentity();
+        final int callingUserId = UserHandle.getCallingUserId();
+        final long token = mInjector.binderClearCallingIdentity();
         try {
             UserInfo currentUser;
             UserInfo callingUser = getUserInfo(callingUserId);
@@ -7796,6 +7811,7 @@
             return false;
         }
 
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         if (packageList != null) {
             // InputMethodManager fetches input methods for current user.
             // So this can only be set when calling user is the current user
@@ -7810,7 +7826,7 @@
                     enabledPackages.add(ime.getPackageName());
                 }
                 if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
-                        mInjector.binderGetCallingUserHandle().getIdentifier())) {
+                        callingUserId)) {
                     Slog.e(LOG_TAG, "Cannot set permitted input methods, "
                             + "because it contains already enabled input method.");
                     return false;
@@ -7822,7 +7838,7 @@
             ActiveAdmin admin = getActiveAdminForCallerLocked(who,
                     DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             admin.permittedInputMethods = packageList;
-            saveSettingsLocked(UserHandle.getCallingUserId());
+            saveSettingsLocked(callingUserId);
         }
         return true;
     }
@@ -7921,11 +7937,70 @@
             if (admin.permittedInputMethods == null) {
                 return true;
             }
-            return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+            return checkPackagesInPermittedListOrSystem(Collections.singletonList(packageName),
                     admin.permittedInputMethods, userHandle);
         }
     }
 
+    @Override
+    public boolean setPermittedCrossProfileNotificationListeners(
+            ComponentName who, List<String> packageList) {
+        if (!mHasFeature) {
+            return false;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        if (!isManagedProfile(callingUserId)) {
+            return false;
+        }
+
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            admin.permittedNotificationListeners = packageList;
+            saveSettingsLocked(callingUserId);
+        }
+        return true;
+    }
+
+    @Override
+    public List<String> getPermittedCrossProfileNotificationListeners(ComponentName who) {
+        if (!mHasFeature) {
+            return null;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            return admin.permittedNotificationListeners;
+        }
+    }
+
+    @Override
+    public boolean isNotificationListenerServicePermitted(String packageName, int userId) {
+        if (!mHasFeature) {
+            return true;
+        }
+
+        Preconditions.checkStringNotEmpty(packageName, "packageName is null or empty");
+        if (!isCallerWithSystemUid()) {
+            throw new SecurityException(
+                    "Only the system can query if a notification listener service is permitted");
+        }
+        synchronized (this) {
+            ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+            if (profileOwner == null || profileOwner.permittedNotificationListeners == null) {
+                return true;
+            }
+            return checkPackagesInPermittedListOrSystem(Collections.singletonList(packageName),
+                    profileOwner.permittedNotificationListeners, userId);
+
+        }
+    }
+
+
     private void sendAdminEnabledBroadcastLocked(int userHandle) {
         DevicePolicyData policyData = getUserData(userHandle);
         if (policyData.mAdminBroadcastPending) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 721d337..10f5ba4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1216,7 +1216,8 @@
     }
 
     /**
-     * Setup a package in the package manager mock. Useful for faking installed applications.
+     * Setup a package in the package manager mock for {@link DpmMockContext#CALLER_USER_HANDLE}.
+     * Useful for faking installed applications.
      *
      * @param packageName the name of the package to be setup
      * @param appId the application ID to be given to the package
@@ -1224,19 +1225,41 @@
      */
     private int setupPackageInPackageManager(final String packageName, final int appId)
             throws Exception {
+        return setupPackageInPackageManager(
+                packageName, DpmMockContext.CALLER_USER_HANDLE, appId,
+                ApplicationInfo.FLAG_HAS_CODE);
+    }
+
+    /**
+     * Setup a package in the package manager mock. Useful for faking installed applications.
+     *
+     * @param packageName the name of the package to be setup
+     * @param userId the user id where the package will be "installed"
+     * @param appId the application ID to be given to the package
+     * @param flags flags to set in the ApplicationInfo for this package
+     * @return the UID of the package as known by the mock package manager
+     */
+    private int setupPackageInPackageManager(
+            final String packageName, int userId, final int appId, int flags)
+            throws Exception {
         // Make the PackageManager return the package instead of throwing a NameNotFoundException
         final PackageInfo pi = new PackageInfo();
         pi.applicationInfo = new ApplicationInfo();
-        pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
+        pi.applicationInfo.flags = flags;
         doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
                 eq(packageName),
                 anyInt(),
-                eq(DpmMockContext.CALLER_USER_HANDLE));
+                eq(userId));
+        doReturn(pi.applicationInfo).when(mContext.ipackageManager).getApplicationInfo(
+                eq(packageName),
+                anyInt(),
+                eq(userId));
+
         // Setup application UID with the PackageManager
-        final int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, appId);
+        final int uid = UserHandle.getUid(userId, appId);
         doReturn(uid).when(mContext.packageManager).getPackageUidAsUser(
                 eq(packageName),
-                eq(DpmMockContext.CALLER_USER_HANDLE));
+                eq(userId));
         // Associate packageName to uid
         doReturn(packageName).when(mContext.ipackageManager).getNameForUid(eq(uid));
         doReturn(new String[]{packageName})
@@ -3970,6 +3993,210 @@
         assertFalse(dpm.isCurrentInputMethodSetByOwner());
     }
 
+    public void testSetPermittedCrossProfileNotificationListeners_unavailableForDo()
+            throws Exception {
+        // Set up a device owner.
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        assertSetPermittedCrossProfileNotificationListenersUnavailable(mContext.binder.callingUid);
+    }
+
+    public void testSetPermittedCrossProfileNotificationListeners_unavailableForPoOnUser()
+            throws Exception {
+        // Set up a profile owner.
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        setupProfileOwner();
+        assertSetPermittedCrossProfileNotificationListenersUnavailable(mContext.binder.callingUid);
+    }
+
+    private void assertSetPermittedCrossProfileNotificationListenersUnavailable(
+            int adminUid) throws Exception {
+        mContext.binder.callingUid = adminUid;
+        final int userId = UserHandle.getUserId(adminUid);
+
+        final String packageName = "some.package";
+        assertFalse(dpms.setPermittedCrossProfileNotificationListeners(
+                admin1, Collections.singletonList(packageName)));
+        assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertTrue(dpms.isNotificationListenerServicePermitted(packageName, userId));
+
+        // Attempt to set to empty list (which means no listener is whitelisted)
+        mContext.binder.callingUid = adminUid;
+        assertFalse(dpms.setPermittedCrossProfileNotificationListeners(
+                admin1, Collections.<String>emptyList()));
+        assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertTrue(dpms.isNotificationListenerServicePermitted(packageName, userId));
+    }
+
+    public void testIsNotificationListenerServicePermitted_onlySystemCanCall() throws Exception {
+        // Set up a managed profile
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+        final String permittedListener = "some.package";
+        setupPackageInPackageManager(
+                permittedListener,
+                UserHandle.USER_SYSTEM, // We check the packageInfo from the primary user.
+                /*appId=*/ 12345, /*flags=*/ 0);
+
+        assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+                admin1, Collections.singletonList(permittedListener)));
+
+        try {
+            dpms.isNotificationListenerServicePermitted(
+                permittedListener, MANAGED_PROFILE_USER_ID);
+            fail("isNotificationListenerServicePermitted should throw if not called from System");
+        } catch (SecurityException expected) {
+        }
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                permittedListener, MANAGED_PROFILE_USER_ID));
+    }
+
+    public void testSetPermittedCrossProfileNotificationListeners_managedProfile()
+            throws Exception {
+        // Set up a managed profile
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+        final String permittedListener = "permitted.package";
+        int appId = 12345;
+        setupPackageInPackageManager(
+                permittedListener,
+                UserHandle.USER_SYSTEM,  // We check the packageInfo from the primary user.
+                appId, /*flags=*/ 0);
+
+        final String notPermittedListener = "not.permitted.package";
+        setupPackageInPackageManager(
+                notPermittedListener,
+                UserHandle.USER_SYSTEM,  // We check the packageInfo from the primary user.
+                ++appId, /*flags=*/ 0);
+
+        final String systemListener = "system.package";
+        setupPackageInPackageManager(
+                systemListener,
+                UserHandle.USER_SYSTEM,  // We check the packageInfo from the primary user.
+                ++appId, ApplicationInfo.FLAG_SYSTEM);
+
+        // By default all packages are allowed
+        assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                permittedListener, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                notPermittedListener, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, MANAGED_PROFILE_USER_ID));
+
+        // Setting only one package in the whitelist
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+                admin1, Collections.singletonList(permittedListener)));
+        List<String> permittedListeners =
+                dpms.getPermittedCrossProfileNotificationListeners(admin1);
+        assertEquals(1, permittedListeners.size());
+        assertEquals(permittedListener, permittedListeners.get(0));
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                permittedListener, MANAGED_PROFILE_USER_ID));
+        assertFalse(dpms.isNotificationListenerServicePermitted(
+                notPermittedListener, MANAGED_PROFILE_USER_ID));
+        // System packages are always allowed (even if not in the whitelist)
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, MANAGED_PROFILE_USER_ID));
+
+        // Setting an empty whitelist - only system listeners allowed
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+                admin1, Collections.<String>emptyList()));
+        assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size());
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertFalse(dpms.isNotificationListenerServicePermitted(
+                permittedListener, MANAGED_PROFILE_USER_ID));
+        assertFalse(dpms.isNotificationListenerServicePermitted(
+                notPermittedListener, MANAGED_PROFILE_USER_ID));
+        // System packages are always allowed (even if not in the whitelist)
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, MANAGED_PROFILE_USER_ID));
+
+        // Setting a null whitelist - all listeners allowed
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        assertTrue(dpms.setPermittedCrossProfileNotificationListeners(admin1, null));
+        assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                permittedListener, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                notPermittedListener, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, MANAGED_PROFILE_USER_ID));
+    }
+
+    public void testSetPermittedCrossProfileNotificationListeners_doesNotAffectPrimaryProfile()
+            throws Exception {
+        // Set up a managed profile
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+        final String nonSystemPackage = "non.system.package";
+        int appId = 12345;
+        setupPackageInPackageManager(
+                nonSystemPackage,
+                UserHandle.USER_SYSTEM,  // We check the packageInfo from the primary user.
+                appId, /*flags=*/ 0);
+
+        final String systemListener = "system.package";
+        setupPackageInPackageManager(
+                systemListener,
+                UserHandle.USER_SYSTEM,  // We check the packageInfo from the primary user.
+                ++appId, ApplicationInfo.FLAG_SYSTEM);
+
+        // By default all packages are allowed (for all profiles)
+        assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                nonSystemPackage, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                nonSystemPackage, UserHandle.USER_SYSTEM));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, UserHandle.USER_SYSTEM));
+
+        // Setting an empty whitelist - only system listeners allowed in managed profile, but
+        // all allowed in primary profile
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+                admin1, Collections.<String>emptyList()));
+        assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size());
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        assertFalse(dpms.isNotificationListenerServicePermitted(
+                nonSystemPackage, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, MANAGED_PROFILE_USER_ID));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                nonSystemPackage, UserHandle.USER_SYSTEM));
+        assertTrue(dpms.isNotificationListenerServicePermitted(
+                systemListener, UserHandle.USER_SYSTEM));
+    }
+
     public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
         mContext.packageName = mRealTestContext.getPackageName();
         setDeviceOwner();