Allow apps to proxy notifications for other apps

This will allow apps to delegate posting to persistently
running apps, to decrease the numbers of times apps need to wake up
just to post a notification.

Bug: 111452544
Test: runtest systemui-notification
Change-Id: I1ead239747f2871f222d0ce6a971d1448a0766ad
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 426a0c15..fd32b5a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5296,7 +5296,9 @@
         try {
             INotificationManager notificationManager = mInjector.getNotificationManager();
             try {
-                notificationManager.enqueueNotificationWithTag(packageName, packageName,
+                // The calling uid must match either the package or op package, so use an op
+                // package that matches the cleared calling identity.
+                notificationManager.enqueueNotificationWithTag(packageName, "android",
                         id.mTag, id.mId, notification, userId);
             } catch (RemoteException e) {
                 /* ignore - local call */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ce71dd2..03b7652 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -35,6 +35,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
@@ -203,6 +205,7 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
 import com.android.server.notification.ManagedServices.UserProfiles;
+import com.android.server.pm.PackageManagerService;
 import com.android.server.policy.PhoneWindowManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -470,8 +473,8 @@
                 // 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);
+                                MATCH_DIRECT_BOOT_AWARE
+                                        | MATCH_DIRECT_BOOT_UNAWARE, userId);
                 for (ComponentName cn : approvedListeners) {
                     try {
                         getBinderService().setNotificationListenerAccessGrantedForUser(cn,
@@ -507,8 +510,8 @@
             // only be one
             Set<ComponentName> approvedAssistants =
                     mAssistants.queryPackageForServices(defaultAssistantAccess,
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                            MATCH_DIRECT_BOOT_AWARE
+                                    | MATCH_DIRECT_BOOT_UNAWARE, userId);
             for (ComponentName cn : approvedAssistants) {
                 try {
                     getBinderService().setNotificationAssistantAccessGrantedForUser(
@@ -1377,7 +1380,7 @@
             NotificationUsageStats usageStats, AtomicFile policyFile,
             ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
             UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
-            IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal) {
+            IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps) {
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1390,7 +1393,7 @@
         mUgmInternal = ugmInternal;
         mPackageManager = packageManager;
         mPackageManagerClient = packageManagerClient;
-        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+        mAppOps = appOps;
         mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
         mAppUsageStats = appUsageStats;
         mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
@@ -1544,7 +1547,8 @@
                 LocalServices.getService(UsageStatsManagerInternal.class),
                 LocalServices.getService(DevicePolicyManagerInternal.class),
                 UriGrantsManager.getService(),
-                LocalServices.getService(UriGrantsManagerInternal.class));
+                LocalServices.getService(UriGrantsManagerInternal.class),
+                (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE));
 
         // register for various Intents
         IntentFilter filter = new IntentFilter();
@@ -2234,6 +2238,60 @@
         }
 
         @Override
+        public void setNotificationDelegate(String callingPkg, String delegate) {
+            checkCallerIsSameApp(callingPkg);
+            final int callingUid = Binder.getCallingUid();
+            UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+            try {
+                ApplicationInfo info =
+                        mPackageManager.getApplicationInfo(delegate,
+                                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                                user.getIdentifier());
+                if (info != null) {
+                    mPreferencesHelper.setNotificationDelegate(
+                            callingPkg, callingUid, delegate, info.uid);
+                    savePolicyFile();
+                }
+            } catch (RemoteException e) {
+                // :(
+            }
+        }
+
+        @Override
+        public void revokeNotificationDelegate(String callingPkg) {
+            checkCallerIsSameApp(callingPkg);
+            mPreferencesHelper.revokeNotificationDelegate(callingPkg, Binder.getCallingUid());
+            savePolicyFile();
+        }
+
+        @Override
+        public String getNotificationDelegate(String callingPkg) {
+            // callable by Settings also
+            checkCallerIsSystemOrSameApp(callingPkg);
+            return mPreferencesHelper.getNotificationDelegate(callingPkg, Binder.getCallingUid());
+        }
+
+        @Override
+        public boolean canNotifyAsPackage(String callingPkg, String targetPkg) {
+            checkCallerIsSameApp(callingPkg);
+            final int callingUid = Binder.getCallingUid();
+            UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+            try {
+                ApplicationInfo info =
+                        mPackageManager.getApplicationInfo(targetPkg,
+                                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                                user.getIdentifier());
+                if (info != null) {
+                    return mPreferencesHelper.isDelegateAllowed(
+                            targetPkg, info.uid, callingPkg, callingUid);
+                }
+            } catch (RemoteException e) {
+                // :(
+            }
+            return false;
+        }
+
+        @Override
         public void updateNotificationChannelGroupForPackage(String pkg, int uid,
                 NotificationChannelGroup group) throws RemoteException {
             enforceSystemOrSystemUI("Caller not system or systemui");
@@ -4053,20 +4111,21 @@
             Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                     + " notification=" + notification);
         }
-        checkCallerIsSystemOrSameApp(pkg);
-        checkRestrictedCategories(notification);
-
-        final int userId = ActivityManager.handleIncomingUser(callingPid,
-                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
-        final UserHandle user = new UserHandle(userId);
 
         if (pkg == null || notification == null) {
             throw new IllegalArgumentException("null not allowed: pkg=" + pkg
                     + " id=" + id + " notification=" + notification);
         }
 
-        // The system can post notifications for any package, let us resolve that.
-        final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);
+        final int userId = ActivityManager.handleIncomingUser(callingPid,
+                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
+        final UserHandle user = UserHandle.of(userId);
+
+        // Can throw a SecurityException if the calling uid doesn't have permission to post
+        // as "pkg"
+        final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+
+        checkRestrictedCategories(notification);
 
         // Fix the notification as best we can.
         try {
@@ -4193,17 +4252,28 @@
         }
     }
 
-    private int resolveNotificationUid(String opPackageName, int callingUid, int userId) {
-        // The system can post notifications on behalf of any package it wants
-        if (isCallerSystemOrPhone() && opPackageName != null && !"android".equals(opPackageName)) {
-            try {
-                return getContext().getPackageManager()
-                        .getPackageUidAsUser(opPackageName, userId);
-            } catch (NameNotFoundException e) {
-                /* ignore */
-            }
+    @VisibleForTesting
+    int resolveNotificationUid(String callingPkg, String targetPkg,
+            int callingUid, int userId) {
+        // posted from app A on behalf of app A
+        if (isCallerSameApp(targetPkg, callingUid) && TextUtils.equals(callingPkg, targetPkg)) {
+            return callingUid;
         }
-        return callingUid;
+
+        int targetUid = -1;
+        try {
+            targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
+        } catch (NameNotFoundException e) {
+            /* ignore */
+        }
+        // posted from app A on behalf of app B
+        if (targetUid != -1 && (isCallerAndroid(callingPkg, callingUid)
+                || mPreferencesHelper.isDelegateAllowed(
+                        targetPkg, targetUid, callingPkg, callingUid))) {
+            return targetUid;
+        }
+
+        throw new SecurityException("Caller " + callingUid + " cannot post for pkg " + targetPkg);
     }
 
     /**
@@ -4222,7 +4292,8 @@
         // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.
         if (!isSystemNotification && !isNotificationFromListener) {
             synchronized (mNotificationLock) {
-                if (mNotificationsByKey.get(r.sbn.getKey()) == null && isCallerInstantApp(pkg)) {
+                if (mNotificationsByKey.get(r.sbn.getKey()) == null
+                        && isCallerInstantApp(pkg, callingUid)) {
                     // Ephemeral apps have some special constraints for notifications.
                     // They are not allowed to create new notifications however they are allowed to
                     // update notifications created by the system (e.g. a foreground service
@@ -5149,11 +5220,11 @@
                     try {
                         Thread.sleep(waitMs);
                     } catch (InterruptedException e) { }
-                    mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                    mVibrator.vibrate(record.sbn.getUid(), record.sbn.getPackageName(),
                             effect, "Notification (delayed)", record.getAudioAttributes());
                 }).start();
             } else {
-                mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                mVibrator.vibrate(record.sbn.getUid(), record.sbn.getPackageName(),
                         effect, "Notification", record.getAudioAttributes());
             }
             return true;
@@ -6282,6 +6353,11 @@
         checkCallerIsSameApp(pkg);
     }
 
+    private boolean isCallerAndroid(String callingPkg, int uid) {
+        return isUidSystemOrPhone(uid) && callingPkg != null
+                && PackageManagerService.PLATFORM_PACKAGE_NAME.equals(callingPkg);
+    }
+
     /**
      * Check if the notification is of a category type that is restricted to system use only,
      * if so throw SecurityException
@@ -6302,13 +6378,13 @@
         }
     }
 
-    private boolean isCallerInstantApp(String pkg) {
+    private boolean isCallerInstantApp(String pkg, int callingUid) {
         // System is always allowed to act for ephemeral apps.
-        if (isCallerSystemOrPhone()) {
+        if (isUidSystemOrPhone(callingUid)) {
             return false;
         }
 
-        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        mAppOps.checkPackage(callingUid, pkg);
 
         try {
             ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0,
@@ -6324,7 +6400,10 @@
     }
 
     private void checkCallerIsSameApp(String pkg) {
-        final int uid = Binder.getCallingUid();
+        checkCallerIsSameApp(pkg, Binder.getCallingUid());
+    }
+
+    private void checkCallerIsSameApp(String pkg, int uid) {
         try {
             ApplicationInfo ai = mPackageManager.getApplicationInfo(
                     pkg, 0, UserHandle.getCallingUserId());
@@ -6340,6 +6419,24 @@
         }
     }
 
+    private boolean isCallerSameApp(String pkg) {
+        try {
+            checkCallerIsSameApp(pkg);
+            return true;
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
+    private boolean isCallerSameApp(String pkg, int uid) {
+        try {
+            checkCallerIsSameApp(pkg, uid);
+            return true;
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
     private static String callStateToString(int state) {
         switch (state) {
             case TelephonyManager.CALL_STATE_IDLE: return "CALL_STATE_IDLE";
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 432d17c..593e7cd 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -20,6 +20,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
@@ -66,12 +67,14 @@
 public class PreferencesHelper implements RankingConfig {
     private static final String TAG = "NotificationPrefHelper";
     private static final int XML_VERSION = 1;
+    private static final int UNKNOWN_UID = UserHandle.USER_NULL;
 
     @VisibleForTesting
     static final String TAG_RANKING = "ranking";
     private static final String TAG_PACKAGE = "package";
     private static final String TAG_CHANNEL = "channel";
     private static final String TAG_GROUP = "channelGroup";
+    private static final String TAG_DELEGATE = "delegate";
 
     private static final String ATT_VERSION = "version";
     private static final String ATT_NAME = "name";
@@ -82,6 +85,8 @@
     private static final String ATT_IMPORTANCE = "importance";
     private static final String ATT_SHOW_BADGE = "show_badge";
     private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
+    private static final String ATT_ENABLED = "enabled";
+    private static final String ATT_USER_ALLOWED = "allowed";
 
     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
@@ -147,8 +152,7 @@
             }
             if (type == XmlPullParser.START_TAG) {
                 if (TAG_PACKAGE.equals(tag)) {
-                    int uid = XmlUtils.readIntAttribute(parser, ATT_UID,
-                            PackagePreferences.UNKNOWN_UID);
+                    int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
                     String name = parser.getAttributeValue(null, ATT_NAME);
                     if (!TextUtils.isEmpty(name)) {
                         if (forRestore) {
@@ -217,6 +221,24 @@
                                     r.channels.put(id, channel);
                                 }
                             }
+                            // Delegate
+                            if (TAG_DELEGATE.equals(tagName)) {
+                                int delegateId =
+                                        XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
+                                String delegateName =
+                                        XmlUtils.readStringAttribute(parser, ATT_NAME);
+                                boolean delegateEnabled = XmlUtils.readBooleanAttribute(
+                                        parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
+                                boolean userAllowed = XmlUtils.readBooleanAttribute(
+                                        parser, ATT_USER_ALLOWED, Delegate.DEFAULT_USER_ALLOWED);
+                                Delegate d = null;
+                                if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(delegateName)) {
+                                    d = new Delegate(
+                                            delegateName, delegateId, delegateEnabled, userAllowed);
+                                }
+                                r.delegate = d;
+                            }
+
                         }
 
                         try {
@@ -248,7 +270,7 @@
         final String key = packagePreferencesKey(pkg, uid);
         synchronized (mPackagePreferencess) {
             PackagePreferences
-                    r = (uid == PackagePreferences.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
+                    r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
                     : mPackagePreferencess.get(key);
             if (r == null) {
                 r = new PackagePreferences();
@@ -265,7 +287,7 @@
                     Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
                 }
 
-                if (r.uid == PackagePreferences.UNKNOWN_UID) {
+                if (r.uid == UNKNOWN_UID) {
                     mRestoredWithoutUids.put(pkg, r);
                 } else {
                     mPackagePreferencess.put(key, r);
@@ -357,7 +379,8 @@
                                 || r.showBadge != DEFAULT_SHOW_BADGE
                                 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
                                 || r.channels.size() > 0
-                                || r.groups.size() > 0;
+                                || r.groups.size() > 0
+                                || r.delegate != null;
                 if (hasNonDefaultSettings) {
                     out.startTag(null, TAG_PACKAGE);
                     out.attribute(null, ATT_NAME, r.pkg);
@@ -378,6 +401,21 @@
                         out.attribute(null, ATT_UID, Integer.toString(r.uid));
                     }
 
+                    if (r.delegate != null) {
+                        out.startTag(null, TAG_DELEGATE);
+
+                        out.attribute(null, ATT_NAME, r.delegate.mPkg);
+                        out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
+                        if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
+                            out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
+                        }
+                        if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
+                            out.attribute(null, ATT_USER_ALLOWED,
+                                    Boolean.toString(r.delegate.mUserAllowed));
+                        }
+                        out.endTag(null, TAG_DELEGATE);
+                    }
+
                     for (NotificationChannelGroup group : r.groups.values()) {
                         group.writeXml(out);
                     }
@@ -923,16 +961,76 @@
      * considered for sentiment adjustments (and thus never show a blocking helper).
      */
     public void setAppImportanceLocked(String packageName, int uid) {
-        PackagePreferences PackagePreferences = getOrCreatePackagePreferences(packageName, uid);
-        if ((PackagePreferences.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
+        PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
+        if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
             return;
         }
 
-        PackagePreferences.lockedAppFields =
-                PackagePreferences.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
+        prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
         updateConfig();
     }
 
+    /**
+     * Returns the delegate for a given package, if it's allowed by the package and the user.
+     */
+    public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
+        PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+
+        if (prefs == null || prefs.delegate == null) {
+            return null;
+        }
+        if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
+            return null;
+        }
+        return prefs.delegate.mPkg;
+    }
+
+    /**
+     * Used by an app to delegate notification posting privileges to another apps.
+     */
+    public void setNotificationDelegate(String sourcePkg, int sourceUid,
+            String delegatePkg, int delegateUid) {
+        PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
+
+        boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
+        Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
+        prefs.delegate = delegate;
+        updateConfig();
+    }
+
+    /**
+     * Used by an app to turn off its notification delegate.
+     */
+    public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
+        PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+        if (prefs != null && prefs.delegate != null) {
+            prefs.delegate.mEnabled = false;
+            updateConfig();
+        }
+    }
+
+    /**
+     * Toggles whether an app can have a notification delegate on behalf of a user.
+     */
+    public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
+        PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+        if (prefs != null && prefs.delegate != null) {
+            prefs.delegate.mUserAllowed = userAllowed;
+            updateConfig();
+        }
+    }
+
+    /**
+     * Returns whether the given app is allowed on post notifications on behalf of the other given
+     * app.
+     */
+    public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
+            String potentialDelegatePkg, int potentialDelegateUid) {
+        PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+
+        return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
+    }
+
     @VisibleForTesting
     void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
         if (original.canBypassDnd() != update.canBypassDnd()) {
@@ -994,8 +1092,7 @@
                 pw.print("  AppSettings: ");
                 pw.print(r.pkg);
                 pw.print(" (");
-                pw.print(r.uid == PackagePreferences.UNKNOWN_UID ? "UNKNOWN_UID"
-                        : Integer.toString(r.uid));
+                pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
                 pw.print(')');
                 if (r.importance != DEFAULT_IMPORTANCE) {
                     pw.print(" importance=");
@@ -1356,8 +1453,6 @@
     }
 
     private static class PackagePreferences {
-        static int UNKNOWN_UID = UserHandle.USER_NULL;
-
         String pkg;
         int uid = UNKNOWN_UID;
         int importance = DEFAULT_IMPORTANCE;
@@ -1366,7 +1461,37 @@
         boolean showBadge = DEFAULT_SHOW_BADGE;
         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
 
+        Delegate delegate = null;
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
         Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
+
+        public boolean isValidDelegate(String pkg, int uid) {
+            return delegate != null && delegate.isAllowed(pkg, uid);
+        }
+    }
+
+    private static class Delegate {
+        static final boolean DEFAULT_ENABLED = true;
+        static final boolean DEFAULT_USER_ALLOWED = true;
+        String mPkg;
+        int mUid = UNKNOWN_UID;
+        boolean mEnabled = DEFAULT_ENABLED;
+        boolean mUserAllowed = DEFAULT_USER_ALLOWED;
+
+        Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
+            mPkg = pkg;
+            mUid = uid;
+            mEnabled = enabled;
+            mUserAllowed = userAllowed;
+        }
+
+        public boolean isAllowed(String pkg, int uid) {
+            if (pkg == null || uid == UNKNOWN_UID) {
+                return false;
+            }
+            return pkg.equals(mPkg)
+                    && uid == mUid
+                    && (mUserAllowed && mEnabled);
+        }
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 0ff124e..a1b3b98 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -65,6 +65,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.Application;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -195,6 +197,8 @@
     IUriGrantsManager mUgm;
     @Mock
     UriGrantsManagerInternal mUgmInternal;
+    @Mock
+    AppOpsManager mAppOpsManager;
 
     // Use a Testable subclass so we can simulate calls from the system without failing.
     private static class TestableNotificationManagerService extends NotificationManagerService {
@@ -295,7 +299,8 @@
                     mListeners, mAssistants, mConditionProviders,
                     mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
                     mGroupHelper, mAm, mAppUsageStats,
-                    mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal);
+                    mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
+                    mAppOpsManager);
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                 throw e;
@@ -531,7 +536,7 @@
         mBinderService.createNotificationChannels(
                 PKG, new ParceledListSlice(Arrays.asList(channel)));
         final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -549,7 +554,7 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(1, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -578,7 +583,7 @@
 
         StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         // The first time a foreground service notification is shown, we allow the channel
@@ -600,7 +605,7 @@
 
         sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         // The second time it is shown, we keep the user's preference.
@@ -631,7 +636,7 @@
         mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -645,7 +650,7 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -667,7 +672,7 @@
             final StatusBarNotification sbn =
                     generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
             sbn.getNotification().category = category;
-            mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         waitForIdle();
@@ -691,7 +696,7 @@
             final StatusBarNotification sbn =
                     generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
             sbn.getNotification().category = category;
-            mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         waitForIdle();
@@ -714,7 +719,7 @@
             final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
             sbn.getNotification().category = category;
             try {
-                mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                         sbn.getId(), sbn.getNotification(), sbn.getUserId());
                 fail("Calls from non system apps should not allow use of restricted categories");
             } catch (SecurityException e) {
@@ -746,7 +751,7 @@
 
     @Test
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
@@ -756,7 +761,7 @@
 
     @Test
     public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
         waitForIdle();
@@ -768,10 +773,10 @@
 
     @Test
     public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
         waitForIdle();
@@ -788,7 +793,7 @@
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         NotificationRecord r = generateNotificationRecord(null);
         final StatusBarNotification sbn = r.sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelNotificationsFromListener(null, null);
         waitForIdle();
@@ -801,7 +806,7 @@
     @Test
     public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -816,7 +821,7 @@
         final NotificationRecord n = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group", true);
 
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId());
         waitForIdle();
 
@@ -839,9 +844,9 @@
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group1", false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         waitForIdle();
 
@@ -854,7 +859,7 @@
     public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         for (int i = 0; i < 10; i++) {
-            mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
@@ -873,17 +878,17 @@
                 mTestNotificationChannel, 2, "group1", false);
 
         // fully post parent notification
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
         waitForIdle();
 
         // enqueue the child several times
         for (int i = 0; i < 10; i++) {
-            mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                     child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         }
         // make the parent a child, which will cancel the child notification
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(),
                 parentAsChild.sbn.getUserId());
         waitForIdle();
@@ -895,7 +900,7 @@
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -909,7 +914,7 @@
     public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
         waitForIdle();
@@ -922,7 +927,7 @@
     @Test
     public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
         waitForIdle();
@@ -935,7 +940,7 @@
     @Test
     public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), UserHandle.USER_ALL);
         // Null pkg is how we signal a user switch.
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -950,7 +955,7 @@
     public void testAppInitiatedCancelAllNotifications_CancelsNoClearFlag() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_NO_CLEAR;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1037,7 +1042,7 @@
     public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mInternalService.removeForegroundServiceFlagFromNotification(PKG, sbn.getId(),
                 sbn.getUserId());
@@ -1052,10 +1057,10 @@
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags =
                 Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
         waitForIdle();
@@ -1145,21 +1150,21 @@
         // should not be returned
         final NotificationRecord group2 = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group2", true);
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 group2.sbn.getId(), group2.sbn.getNotification(), group2.sbn.getUserId());
         waitForIdle();
 
         // should not be returned
         final NotificationRecord nonGroup = generateNotificationRecord(
                 mTestNotificationChannel, 3, null, false);
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 nonGroup.sbn.getId(), nonGroup.sbn.getNotification(), nonGroup.sbn.getUserId());
         waitForIdle();
 
         // same group, child, should be returned
         final NotificationRecord group1Child = generateNotificationRecord(
                 mTestNotificationChannel, 4, "group1", false);
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null, group1Child.sbn.getId(),
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null, group1Child.sbn.getId(),
                 group1Child.sbn.getNotification(), group1Child.sbn.getUserId());
         waitForIdle();
 
@@ -1216,7 +1221,7 @@
     public void testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1333,7 +1338,7 @@
                         new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean());
@@ -1348,7 +1353,7 @@
                 mTestNotificationChannel);
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
@@ -1879,7 +1884,7 @@
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         waitForIdle();
 
@@ -1892,7 +1897,7 @@
         final NotificationRecord record = generateNotificationRecord(
                 mTestNotificationChannel, 2, null, false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 record.sbn.getId(), record.sbn.getNotification(), record.sbn.getUserId());
         waitForIdle();
 
@@ -1904,7 +1909,7 @@
         final NotificationRecord parent = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", true);
 
-        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
         waitForIdle();
 
@@ -2378,12 +2383,12 @@
     @Test
     public void testBumpFGImportance_noChannelChangePreOApp() throws Exception {
         String preOPkg = PKG_N_MR1;
-        int preOUid = 145;
         final ApplicationInfo legacy = new ApplicationInfo();
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
         when(mPackageManagerClient.getApplicationInfoAsUser(eq(preOPkg), anyInt(), anyInt()))
                 .thenReturn(legacy);
-        when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt())).thenReturn(preOUid);
+        when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt()))
+                .thenReturn(Binder.getCallingUid());
         getContext().setMockPackageManager(mPackageManagerClient);
 
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -2393,12 +2398,13 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", preOUid,
-                0, nb.build(), new UserHandle(preOUid), null, 0);
+        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag",
+                Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
 
-        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
-                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(),
+                sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
+
         assertEquals(IMPORTANCE_LOW,
                 mService.getNotificationRecord(sbn.getKey()).getImportance());
 
@@ -2408,8 +2414,8 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", preOUid,
-                0, nb.build(), new UserHandle(preOUid), null, 0);
+        sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", Binder.getCallingUid(),
+                0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
 
         mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -3360,7 +3366,7 @@
     }
 
     @Test
-    public void testMybeRecordInterruptionLocked_doesNotRecordTwice()
+    public void testMaybeRecordInterruptionLocked_doesNotRecordTwice()
             throws RemoteException {
         final NotificationRecord r = generateNotificationRecord(
                 mTestNotificationChannel, 1, null, true);
@@ -3373,4 +3379,78 @@
         verify(mAppUsageStats, times(1)).reportInterruptiveNotification(
                 anyString(), anyString(), anyInt());
     }
+
+    @Test
+    public void testResolveNotificationUid_sameApp() throws Exception {
+        ApplicationInfo info = new ApplicationInfo();
+        info.uid = Binder.getCallingUid();
+        when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info);
+
+        int actualUid = mService.resolveNotificationUid("caller", "caller", info.uid, 0);
+
+        assertEquals(info.uid, actualUid);
+    }
+
+    @Test
+    public void testResolveNotificationUid_sameAppWrongPkg() throws Exception {
+        ApplicationInfo info = new ApplicationInfo();
+        info.uid = Binder.getCallingUid();
+        when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info);
+
+        try {
+            mService.resolveNotificationUid("caller", "other", info.uid, 0);
+            fail("Incorrect pkg didn't throw security exception");
+        } catch (SecurityException e) {
+            // yay
+        }
+    }
+
+    @Test
+    public void testResolveNotificationUid_sameAppWrongUid() throws Exception {
+        ApplicationInfo info = new ApplicationInfo();
+        info.uid = 1356347;
+        when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info);
+
+        try {
+            mService.resolveNotificationUid("caller", "caller", 9, 0);
+            fail("Incorrect uid didn't throw security exception");
+        } catch (SecurityException e) {
+            // yay
+        }
+    }
+
+    @Test
+    public void testResolveNotificationUid_delegateAllowed() throws Exception {
+        int expectedUid = 123;
+
+        when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(expectedUid);
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.isDelegateAllowed(anyString(), anyInt(), anyString(), anyInt()))
+                .thenReturn(true);
+
+        assertEquals(expectedUid, mService.resolveNotificationUid("caller", "target", 9, 0));
+    }
+
+    @Test
+    public void testResolveNotificationUid_androidAllowed() throws Exception {
+        int expectedUid = 123;
+
+        when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(expectedUid);
+        // no delegate
+
+        assertEquals(expectedUid, mService.resolveNotificationUid("android", "target", 0, 0));
+    }
+
+    @Test
+    public void testResolveNotificationUid_delegateNotAllowed() throws Exception {
+        when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(123);
+        // no delegate
+
+        try {
+            mService.resolveNotificationUid("caller", "target", 9, 0);
+            fail("Incorrect uid didn't throw security exception");
+        } catch (SecurityException e) {
+            // yay
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 73adf25..750345b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -123,7 +123,6 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        UserHandle user = UserHandle.ALL;
 
         final ApplicationInfo legacy = new ApplicationInfo();
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -176,11 +175,6 @@
                 .build();
     }
 
-    private NotificationChannel getDefaultChannel() {
-        return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
-                IMPORTANCE_LOW);
-    }
-
     private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
             String... channelIds)
             throws Exception {
@@ -1787,4 +1781,159 @@
         mHelper.setEnabled(PKG_N_MR1, 1000, true);
         assertEquals(3, mHelper.getBlockedAppCount(0));
     }
+
+    @Test
+    public void testSetNotificationDelegate() {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testRevokeNotificationDelegate() {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testRevokeNotificationDelegate_noDelegateExistsNoCrash() {
+        mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testToggleNotificationDelegate() {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testToggleNotificationDelegate_noDelegateExistsNoCrash() {
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testIsDelegateAllowed_noSource() {
+        assertFalse(mHelper.isDelegateAllowed("does not exist", -1, "whatever", 0));
+    }
+
+    @Test
+    public void testIsDelegateAllowed_noDelegate() {
+        mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);
+
+        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "whatever", 0));
+    }
+
+    @Test
+    public void testIsDelegateAllowed_delegateDisabledByApp() {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
+    }
+
+    @Test
+    public void testIsDelegateAllowed_wrongDelegate() {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "banana", 27));
+    }
+
+    @Test
+    public void testIsDelegateAllowed_delegateDisabledByUser() {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+
+        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
+    }
+
+    @Test
+    public void testIsDelegateAllowed() {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+
+        assertTrue(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
+    }
+
+    @Test
+    public void testDelegateXml_noDelegate() throws Exception {
+        mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        loadStreamXml(baos, false);
+
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testDelegateXml_delegate() throws Exception {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        loadStreamXml(baos, false);
+
+        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testDelegateXml_disabledDelegate() throws Exception {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        loadStreamXml(baos, false);
+
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testDelegateXml_userDisabledDelegate() throws Exception {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        loadStreamXml(baos, false);
+
+        // appears disabled
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+        // but was loaded and can be toggled back on
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testDelegateXml_entirelyDisabledDelegate() throws Exception {
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+        mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        loadStreamXml(baos, false);
+
+        // appears disabled
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+    }
 }