OSDN Git Service

[RESTRICT_AUTOMERGE]: Add cross user permission check - areNotificationsEnabledForPackage
[android-x86/frameworks-base.git] / services / core / java / com / android / server / notification / NotificationManagerService.java
index b1fd265..4baec05 100644 (file)
 
 package com.android.server.notification;
 
-import static android.service.notification.NotificationRankerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationRankerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CANCEL;
-import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CANCEL_ALL;
-import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CLICK;
-import static android.service.notification.NotificationRankerService.REASON_DELEGATE_ERROR;
-import static android.service.notification.NotificationRankerService.REASON_GROUP_SUMMARY_CANCELED;
-import static android.service.notification.NotificationRankerService.REASON_LISTENER_CANCEL;
-import static android.service.notification.NotificationRankerService.REASON_LISTENER_CANCEL_ALL;
-import static android.service.notification.NotificationRankerService.REASON_PACKAGE_BANNED;
-import static android.service.notification.NotificationRankerService.REASON_PACKAGE_CHANGED;
-import static android.service.notification.NotificationRankerService.REASON_PACKAGE_SUSPENDED;
-import static android.service.notification.NotificationRankerService.REASON_PROFILE_TURNED_OFF;
-import static android.service.notification.NotificationRankerService.REASON_UNAUTOBUNDLED;
-import static android.service.notification.NotificationRankerService.REASON_USER_STOPPED;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_TELEVISION;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_NULL;
+import static android.service.notification.NotificationListenerService
+        .NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
+import static android.service.notification.NotificationListenerService
+        .NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
+import static android.service.notification.NotificationListenerService
+        .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_ERROR;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_CHANGED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
+import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
+import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
+import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
+import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
+import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
@@ -38,30 +55,33 @@ import static android.service.notification.NotificationListenerService.SUPPRESSE
 import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.service.notification.NotificationListenerService.TRIM_FULL;
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
-import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
-import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_NONE;
 
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
+import android.app.NotificationChannelGroup;
+import android.app.backup.BackupManager;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
 import android.app.ITransientNotification;
 import android.app.Notification;
-import android.app.NotificationManager;
+import android.app.NotificationChannel;
 import android.app.NotificationManager.Policy;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
-import android.app.backup.BackupManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
+import android.companion.ICompanionDeviceManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -70,20 +90,18 @@ import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
-import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
-import android.media.AudioSystem;
 import android.media.IRingtonePlayer;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -94,22 +112,32 @@ import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
-import android.service.notification.NotificationRankerService;
+import android.service.notification.NotificationAssistantService;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRankingUpdate;
+import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationServiceDumpProto;
+import android.service.notification.NotificationServiceProto;
+import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeProto;
+import android.telecom.TelecomManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -120,17 +148,25 @@ import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
-import android.view.WindowManager;
+import android.util.proto.ProtoOutputStream;
 import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
 import com.android.server.DeviceIdleController;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
@@ -154,21 +190,21 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -180,7 +216,7 @@ public class NotificationManagerService extends SystemService {
             = SystemProperties.getBoolean("debug.child_notifs", true);
 
     static final int MAX_PACKAGE_NOTIFICATIONS = 50;
-    static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 10f;
+    static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 5f;
 
     // message codes
     static final int MESSAGE_TIMEOUT = 2;
@@ -198,11 +234,12 @@ public class NotificationManagerService extends SystemService {
 
     static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
 
+    static final long SNOOZE_UNTIL_UNSPECIFIED = -1;
+
     static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
 
     static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
 
-    static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
     static final boolean ENABLE_BLOCKED_TOASTS = true;
 
     // When #matchesCallFilter is called from the ringer, wait at most
@@ -224,27 +261,35 @@ public class NotificationManagerService extends SystemService {
     /** notification_enqueue status value for an ignored notification. */
     private static final int EVENTLOG_ENQUEUE_STATUS_IGNORED = 2;
     private static final long MIN_PACKAGE_OVERRATE_LOG_INTERVAL = 5000; // milliseconds
-    private String mRankerServicePackageName;
+
+    private static final long DELAY_FOR_ASSISTANT_TIME = 100;
+
+    private static final String ACTION_NOTIFICATION_TIMEOUT =
+            NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
+    private static final int REQUEST_CODE_TIMEOUT = 1;
+    private static final String SCHEME_TIMEOUT = "timeout";
+    private static final String EXTRA_KEY = "key";
 
     private IActivityManager mAm;
+    private ActivityManager mActivityManager;
+    private IPackageManager mPackageManager;
+    private PackageManager mPackageManagerClient;
     AudioManager mAudioManager;
     AudioManagerInternal mAudioManagerInternal;
     @Nullable StatusBarManagerInternal mStatusBar;
     Vibrator mVibrator;
     private WindowManagerInternal mWindowManagerInternal;
+    private AlarmManager mAlarmManager;
+    private ICompanionDeviceManager mCompanionManager;
+    private AccessibilityManager mAccessibilityManager;
 
     final IBinder mForegroundToken = new Binder();
-    private Handler mHandler;
+    private WorkerHandler mHandler;
     private final HandlerThread mRankingThread = new HandlerThread("ranker",
             Process.THREAD_PRIORITY_BACKGROUND);
 
     private Light mNotificationLight;
     Light mAttentionLight;
-    private int mDefaultNotificationColor;
-    private int mDefaultNotificationLedOn;
-
-    private int mDefaultNotificationLedOff;
-    private long[] mDefaultVibrationPattern;
 
     private long[] mFallbackVibrationPattern;
     private boolean mUseAttentionLight;
@@ -256,25 +301,32 @@ public class NotificationManagerService extends SystemService {
     private String mVibrateNotificationKey;
 
     private final SparseArray<ArraySet<ManagedServiceInfo>> mListenersDisablingEffects =
-            new SparseArray<ArraySet<ManagedServiceInfo>>();
-    private List<ComponentName> mEffectsSuppressors = new ArrayList<ComponentName>();
+            new SparseArray<>();
+    private List<ComponentName> mEffectsSuppressors = new ArrayList<>();
     private int mListenerHints;  // right now, all hints are global
     private int mInterruptionFilter = NotificationListenerService.INTERRUPTION_FILTER_UNKNOWN;
 
     // for enabling and disabling notification pulse behavior
     private boolean mScreenOn = true;
-    private boolean mInCall = false;
+    protected boolean mInCall = false;
     private boolean mNotificationPulseEnabled;
 
+    private Uri mInCallNotificationUri;
+    private AudioAttributes mInCallNotificationAudioAttributes;
+    private float mInCallNotificationVolume;
+
     // used as a mutex for access to all active notifications & listeners
-    final ArrayList<NotificationRecord> mNotificationList =
-            new ArrayList<NotificationRecord>();
-    final ArrayMap<String, NotificationRecord> mNotificationsByKey =
-            new ArrayMap<String, NotificationRecord>();
+    final Object mNotificationLock = new Object();
+    @GuardedBy("mNotificationLock")
+    final ArrayList<NotificationRecord> mNotificationList = new ArrayList<>();
+    @GuardedBy("mNotificationLock")
+    final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>();
+    @GuardedBy("mNotificationLock")
+    final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();
+    @GuardedBy("mNotificationLock")
     final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
-    final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
+    final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();
     final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
-    final PolicyAccess mPolicyAccess = new PolicyAccess();
 
     // The last key in this list owns the hardware.
     ArrayList<String> mLights = new ArrayList<>();
@@ -296,16 +348,20 @@ public class NotificationManagerService extends SystemService {
 
     private final UserProfiles mUserProfiles = new UserProfiles();
     private NotificationListeners mListeners;
-    private NotificationRankers mRankerServices;
+    private NotificationAssistants mAssistants;
     private ConditionProviders mConditionProviders;
     private NotificationUsageStats mUsageStats;
 
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
+    private static final IBinder WHITELIST_TOKEN = new Binder();
     private RankingHandler mRankingHandler;
     private long mLastOverRateLogTime;
     private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
-    private String mSystemNotificationSound;
+
+    private SnoozeHelper mSnoozeHelper;
+    private GroupHelper mGroupHelper;
+    private boolean mIsTelevision;
 
     private static class Archive {
         final int mBufferSize;
@@ -355,27 +411,89 @@ public class NotificationManagerService extends SystemService {
 
     }
 
-    private void readPolicyXml(InputStream stream, boolean forRestore)
+    protected void readDefaultApprovedServices(int userId) {
+        String defaultListenerAccess = getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultListenerAccessPackages);
+        if (defaultListenerAccess != null) {
+            for (String whitelisted :
+                    defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
+                // Gather all notification listener components for candidate pkgs.
+                Set<ComponentName> approvedListeners =
+                        mListeners.queryPackageForServices(whitelisted,
+                                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                for (ComponentName cn : approvedListeners) {
+                    try {
+                        getBinderService().setNotificationListenerAccessGrantedForUser(cn,
+                                    userId, true);
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+        String defaultDndAccess = getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultDndAccessPackages);
+        if (defaultListenerAccess != null) {
+            for (String whitelisted :
+                    defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
+                try {
+                    getBinderService().setNotificationPolicyAccessGranted(whitelisted, true);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    void readPolicyXml(InputStream stream, boolean forRestore)
             throws XmlPullParserException, NumberFormatException, IOException {
         final XmlPullParser parser = Xml.newPullParser();
         parser.setInput(stream, StandardCharsets.UTF_8.name());
+        XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY);
+        boolean migratedManagedServices = false;
+        int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
+                mZenModeHelper.readXml(parser, forRestore);
+            } else if (RankingHelper.TAG_RANKING.equals(parser.getName())){
+                mRankingHelper.readXml(parser, forRestore);
+            }
+            // No non-system managed services are allowed on low ram devices
+            if (!ActivityManager.isLowRamDeviceStatic()) {
+                if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
+                    mListeners.readXml(parser);
+                    migratedManagedServices = true;
+                } else if (mAssistants.getConfig().xmlTag.equals(parser.getName())) {
+                    mAssistants.readXml(parser);
+                    migratedManagedServices = true;
+                } else if (mConditionProviders.getConfig().xmlTag.equals(parser.getName())) {
+                    mConditionProviders.readXml(parser);
+                    migratedManagedServices = true;
+                }
+            }
+        }
 
-        while (parser.next() != END_DOCUMENT) {
-            mZenModeHelper.readXml(parser, forRestore);
-            mRankingHelper.readXml(parser, forRestore);
+        if (!migratedManagedServices) {
+            mListeners.migrateToXml();
+            mAssistants.migrateToXml();
+            mConditionProviders.migrateToXml();
+            savePolicyFile();
         }
     }
 
     private void loadPolicyFile() {
         if (DBG) Slog.d(TAG, "loadPolicyFile");
-        synchronized(mPolicyFile) {
+        synchronized (mPolicyFile) {
 
-            FileInputStream infile = null;
+            InputStream infile = null;
             try {
                 infile = mPolicyFile.openRead();
                 readPolicyXml(infile, false /*forRestore*/);
             } catch (FileNotFoundException e) {
                 // No data yet
+                // Load default managed services approvals
+                readDefaultApprovedServices(UserHandle.USER_SYSTEM);
             } catch (IOException e) {
                 Log.wtf(TAG, "Unable to read notification policy", e);
             } catch (NumberFormatException e) {
@@ -423,23 +541,13 @@ public class NotificationManagerService extends SystemService {
         out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
         mZenModeHelper.writeXml(out, forBackup);
         mRankingHelper.writeXml(out, forBackup);
+        mListeners.writeXml(out, forBackup);
+        mAssistants.writeXml(out, forBackup);
+        mConditionProviders.writeXml(out, forBackup);
         out.endTag(null, TAG_NOTIFICATION_POLICY);
         out.endDocument();
     }
 
-    /** Use this when you actually want to post a notification or toast.
-     *
-     * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
-     */
-    private boolean noteNotificationOp(String pkg, int uid) {
-        if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
-                != AppOpsManager.MODE_ALLOWED) {
-            Slog.v(TAG, "notifications are disabled by AppOps for " + pkg);
-            return false;
-        }
-        return true;
-    }
-
     /** Use this to check if a package can post a notification or toast. */
     private boolean checkNotificationOp(String pkg, int uid) {
         return mAppOps.checkOp(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
@@ -450,7 +558,7 @@ public class NotificationManagerService extends SystemService {
     {
         final int pid;
         final String pkg;
-        final ITransientNotification callback;
+        ITransientNotification callback;
         int duration;
         Binder token;
 
@@ -467,6 +575,10 @@ public class NotificationManagerService extends SystemService {
             this.duration = duration;
         }
 
+        void update(ITransientNotification callback) {
+            this.callback = callback;
+        }
+
         void dump(PrintWriter pw, String prefix, DumpFilter filter) {
             if (filter != null && !filter.matches(pkg)) return;
             pw.println(prefix + this);
@@ -483,11 +595,12 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
-    private final NotificationDelegate mNotificationDelegate = new NotificationDelegate() {
+    @VisibleForTesting
+    final NotificationDelegate mNotificationDelegate = new NotificationDelegate() {
 
         @Override
         public void onSetDisabled(int status) {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 mDisableNotificationEffects =
                         (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
                 if (disableNotificationEffects(null) != null) {
@@ -515,21 +628,24 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public void onClearAll(int callingUid, int callingPid, int userId) {
-            synchronized (mNotificationList) {
-                cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null,
+            synchronized (mNotificationLock) {
+                cancelAllLocked(callingUid, callingPid, userId, REASON_CANCEL_ALL, null,
                         /*includeCurrentProfiles*/ true);
             }
         }
 
         @Override
         public void onNotificationClick(int callingUid, int callingPid, String key) {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r == null) {
                     Log.w(TAG, "No notification with key: " + key);
                     return;
                 }
                 final long now = System.currentTimeMillis();
+                MetricsLogger.action(r.getLogMaker(now)
+                        .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+                        .setType(MetricsEvent.TYPE_ACTION));
                 EventLogTags.writeNotificationClicked(key,
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
 
@@ -537,20 +653,24 @@ public class NotificationManagerService extends SystemService {
                 cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
                         sbn.getId(), Notification.FLAG_AUTO_CANCEL,
                         Notification.FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
-                        REASON_DELEGATE_CLICK, null);
+                        REASON_CLICK, null);
             }
         }
 
         @Override
         public void onNotificationActionClick(int callingUid, int callingPid, String key,
                 int actionIndex) {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r == null) {
                     Log.w(TAG, "No notification with key: " + key);
                     return;
                 }
                 final long now = System.currentTimeMillis();
+                MetricsLogger.action(r.getLogMaker(now)
+                        .setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION)
+                        .setType(MetricsEvent.TYPE_ACTION)
+                        .setSubtype(actionIndex));
                 EventLogTags.writeNotificationActionClicked(key, actionIndex,
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
                 // TODO: Log action click via UsageStats.
@@ -562,11 +682,13 @@ public class NotificationManagerService extends SystemService {
                 String pkg, String tag, int id, int userId) {
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
                     Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
-                    true, userId, REASON_DELEGATE_CANCEL, null);
+                    true, userId, REASON_CANCEL, null);
         }
 
         @Override
         public void onPanelRevealed(boolean clearEffects, int items) {
+            MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL);
+            MetricsLogger.histogram(getContext(), "note_load", items);
             EventLogTags.writeNotificationPanelRevealed(items);
             if (clearEffects) {
                 clearEffects();
@@ -575,12 +697,13 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public void onPanelHidden() {
+            MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
             EventLogTags.writeNotificationPanelHidden();
         }
 
         @Override
         public void clearEffects() {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 if (DBG) Slog.d(TAG, "clearEffects");
                 clearSoundLocked();
                 clearVibrateLocked();
@@ -594,10 +717,10 @@ public class NotificationManagerService extends SystemService {
             Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
                     + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
-                    REASON_DELEGATE_ERROR, null);
+                    REASON_ERROR, null);
             long ident = Binder.clearCallingIdentity();
             try {
-                ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
+                ActivityManager.getService().crashApplication(uid, initialPid, pkg, -1,
                         "Bad notification posted from package " + pkg
                         + ": " + message);
             } catch (RemoteException e) {
@@ -608,7 +731,7 @@ public class NotificationManagerService extends SystemService {
         @Override
         public void onNotificationVisibilityChanged(NotificationVisibility[] newlyVisibleKeys,
                 NotificationVisibility[] noLongerVisibleKeys) {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 for (NotificationVisibility nv : newlyVisibleKeys) {
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
@@ -631,11 +754,17 @@ public class NotificationManagerService extends SystemService {
         @Override
         public void onNotificationExpansionChanged(String key,
                 boolean userAction, boolean expanded) {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r != null) {
                     r.stats.onExpansionChanged(userAction, expanded);
                     final long now = System.currentTimeMillis();
+                    if (userAction) {
+                        MetricsLogger.action(r.getLogMaker(now)
+                                .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+                                .setType(expanded ? MetricsEvent.TYPE_DETAIL
+                                        : MetricsEvent.TYPE_COLLAPSE));
+                    }
                     EventLogTags.writeNotificationExpansion(key,
                             userAction ? 1 : 0, expanded ? 1 : 0,
                             r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
@@ -644,6 +773,7 @@ public class NotificationManagerService extends SystemService {
         }
     };
 
+    @GuardedBy("mNotificationLock")
     private void clearSoundLocked() {
         mSoundNotificationKey = null;
         long identity = Binder.clearCallingIdentity();
@@ -658,6 +788,7 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
+    @GuardedBy("mNotificationLock")
     private void clearVibrateLocked() {
         mVibrateNotificationKey = null;
         long identity = Binder.clearCallingIdentity();
@@ -668,12 +799,66 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
+    @GuardedBy("mNotificationLock")
     private void clearLightsLocked() {
         // light
         mLights.clear();
         updateLightsLocked();
     }
 
+    protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+                mZenModeHelper.updateDefaultZenRules();
+                mRankingHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
+            }
+        }
+    };
+
+    private final BroadcastReceiver mRestoreReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
+                try {
+                    String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+                    String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
+                    int restoredFromSdkInt = intent.getIntExtra(
+                            Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, 0);
+                    mListeners.onSettingRestored(
+                            element, newValue, restoredFromSdkInt, getSendingUserId());
+                    mConditionProviders.onSettingRestored(
+                            element, newValue, restoredFromSdkInt, getSendingUserId());
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Cannot restore managed services from settings", e);
+                }
+            }
+        }
+    };
+
+    private final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+            if (ACTION_NOTIFICATION_TIMEOUT.equals(action)) {
+                final NotificationRecord record;
+                synchronized (mNotificationLock) {
+                    record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY));
+                }
+                if (record != null) {
+                    cancelNotification(record.sbn.getUid(), record.sbn.getInitialPid(),
+                            record.sbn.getPackageName(), record.sbn.getTag(),
+                            record.sbn.getId(), 0,
+                            Notification.FLAG_FOREGROUND_SERVICE, true, record.getUserId(),
+                            REASON_TIMEOUT, null);
+                }
+            }
+        }
+    };
+
     private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -698,16 +883,19 @@ public class NotificationManagerService extends SystemService {
                 int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                         UserHandle.USER_ALL);
                 String pkgList[] = null;
+                int uidList[] = null;
                 boolean removingPackage = queryRemove &&
                         !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                 if (DBG) Slog.i(TAG, "action=" + action + " removing=" + removingPackage);
                 if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                    uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                 } else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) {
                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                     reason = REASON_PACKAGE_SUSPENDED;
                 } else if (queryRestart) {
                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                    uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
                 } else {
                     Uri uri = intent.getData();
                     if (uri == null) {
@@ -720,10 +908,10 @@ public class NotificationManagerService extends SystemService {
                     if (packageChanged) {
                         // We cancel notifications for packages which have just been disabled
                         try {
-                            final IPackageManager pm = AppGlobals.getPackageManager();
-                            final int enabled = pm.getApplicationEnabledSetting(pkgName,
+                            final int enabled = mPackageManager.getApplicationEnabledSetting(
+                                    pkgName,
                                     changeUserId != UserHandle.USER_ALL ? changeUserId :
-                                    UserHandle.USER_SYSTEM);
+                                            UserHandle.USER_SYSTEM);
                             if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                                     || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
                                 cancelNotifications = false;
@@ -739,20 +927,21 @@ public class NotificationManagerService extends SystemService {
                         }
                     }
                     pkgList = new String[]{pkgName};
+                    uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
                 }
-
                 if (pkgList != null && (pkgList.length > 0)) {
                     for (String pkgName : pkgList) {
                         if (cancelNotifications) {
-                            cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart,
-                                    changeUserId, reason, null);
+                            cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
+                                    !queryRestart, changeUserId, reason, null);
                         }
                     }
                 }
-                mListeners.onPackagesChanged(removingPackage, pkgList);
-                mRankerServices.onPackagesChanged(removingPackage, pkgList);
-                mConditionProviders.onPackagesChanged(removingPackage, pkgList);
-                mRankingHelper.onPackagesChanged(removingPackage, pkgList);
+                mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
+                mAssistants.onPackagesChanged(removingPackage, pkgList, uidList);
+                mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
+                mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList, uidList);
+                savePolicyFile();
             }
         }
     };
@@ -777,51 +966,59 @@ public class NotificationManagerService extends SystemService {
             } else if (action.equals(Intent.ACTION_USER_STOPPED)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0) {
-                    cancelAllNotificationsInt(MY_UID, MY_PID, null, 0, 0, true, userHandle,
+                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
                             REASON_USER_STOPPED, null);
                 }
             } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0) {
-                    cancelAllNotificationsInt(MY_UID, MY_PID, null, 0, 0, true, userHandle,
+                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
                             REASON_PROFILE_TURNED_OFF, null);
                 }
             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
                 // turn off LED when user passes through lock screen
                 mNotificationLight.turnOff();
-                if (mStatusBar != null) {
-                    mStatusBar.notificationLightOff();
-                }
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
-                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 // reload per-user settings
                 mSettingsObserver.update(null);
                 mUserProfiles.updateCache(context);
                 // Refresh managed services
                 mConditionProviders.onUserSwitched(user);
                 mListeners.onUserSwitched(user);
-                mRankerServices.onUserSwitched(user);
+                mAssistants.onUserSwitched(user);
                 mZenModeHelper.onUserSwitched(user);
             } else if (action.equals(Intent.ACTION_USER_ADDED)) {
-                mUserProfiles.updateCache(context);
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                if (userId != USER_NULL) {
+                    mUserProfiles.updateCache(context);
+                    if (!mUserProfiles.isManagedProfile(userId)) {
+                        readDefaultApprovedServices(userId);
+                    }
+                }
             } else if (action.equals(Intent.ACTION_USER_REMOVED)) {
-                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 mZenModeHelper.onUserRemoved(user);
+                mRankingHelper.onUserRemoved(user);
+                mListeners.onUserRemoved(user);
+                mConditionProviders.onUserRemoved(user);
+                mAssistants.onUserRemoved(user);
+                savePolicyFile();
             } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
-                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 mConditionProviders.onUserUnlocked(user);
                 mListeners.onUserUnlocked(user);
-                mRankerServices.onUserUnlocked(user);
+                mAssistants.onUserUnlocked(user);
                 mZenModeHelper.onUserUnlocked(user);
             }
         }
     };
 
     private final class SettingsObserver extends ContentObserver {
+        private final Uri NOTIFICATION_BADGING_URI
+                = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
         private final Uri NOTIFICATION_LIGHT_PULSE_URI
                 = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
-        private final Uri NOTIFICATION_SOUND_URI
-                = Settings.System.getUriFor(Settings.System.NOTIFICATION_SOUND);
         private final Uri NOTIFICATION_RATE_LIMIT_URI
                 = Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE);
 
@@ -831,9 +1028,9 @@ public class NotificationManagerService extends SystemService {
 
         void observe() {
             ContentResolver resolver = getContext().getContentResolver();
-            resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
+            resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
                     false, this, UserHandle.USER_ALL);
-            resolver.registerContentObserver(NOTIFICATION_SOUND_URI,
+            resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
                     false, this, UserHandle.USER_ALL);
@@ -847,8 +1044,8 @@ public class NotificationManagerService extends SystemService {
         public void update(Uri uri) {
             ContentResolver resolver = getContext().getContentResolver();
             if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
-                boolean pulseEnabled = Settings.System.getInt(resolver,
-                            Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
+                boolean pulseEnabled = Settings.System.getIntForUser(resolver,
+                            Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0;
                 if (mNotificationPulseEnabled != pulseEnabled) {
                     mNotificationPulseEnabled = pulseEnabled;
                     updateNotificationPulse();
@@ -858,24 +1055,14 @@ public class NotificationManagerService extends SystemService {
                 mMaxPackageEnqueueRate = Settings.Global.getFloat(resolver,
                             Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, mMaxPackageEnqueueRate);
             }
-            if (uri == null || NOTIFICATION_SOUND_URI.equals(uri)) {
-                mSystemNotificationSound = Settings.System.getString(resolver,
-                        Settings.System.NOTIFICATION_SOUND);
+            if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
+                mRankingHelper.updateBadgingEnabled();
             }
         }
     }
 
     private SettingsObserver mSettingsObserver;
-    private ZenModeHelper mZenModeHelper;
-
-    private final Runnable mBuzzBeepBlinked = new Runnable() {
-        @Override
-        public void run() {
-            if (mStatusBar != null) {
-                mStatusBar.buzzBeepBlinked();
-            }
-        }
-    };
+    protected ZenModeHelper mZenModeHelper;
 
     static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
         int[] ar = r.getIntArray(resid);
@@ -892,8 +1079,10 @@ public class NotificationManagerService extends SystemService {
 
     public NotificationManagerService(Context context) {
         super(context);
+        Notification.processWhitelistToken = WHITELIST_TOKEN;
     }
 
+    // TODO - replace these methods with a single VisibleForTesting constructor
     @VisibleForTesting
     void setAudioManager(AudioManager audioMananger) {
         mAudioManager = audioMananger;
@@ -905,38 +1094,135 @@ public class NotificationManagerService extends SystemService {
     }
 
     @VisibleForTesting
+    void setLights(Light light) {
+        mNotificationLight = light;
+        mAttentionLight = light;
+        mNotificationPulseEnabled = true;
+    }
+
+    @VisibleForTesting
+    void setScreenOn(boolean on) {
+        mScreenOn = on;
+    }
+
+    @VisibleForTesting
+    int getNotificationRecordCount() {
+        synchronized (mNotificationLock) {
+            int count = mNotificationList.size() + mNotificationsByKey.size()
+                    + mSummaryByGroupKey.size() + mEnqueuedNotifications.size();
+            // subtract duplicates
+            for (NotificationRecord posted : mNotificationList) {
+                if (mNotificationsByKey.containsKey(posted.getKey())) {
+                    count--;
+                }
+                if (posted.sbn.isGroup() && posted.getNotification().isGroupSummary()) {
+                    count--;
+                }
+            }
+
+            return count;
+        }
+    }
+
+    void clearNotifications() {
+        mEnqueuedNotifications.clear();
+        mNotificationList.clear();
+        mNotificationsByKey.clear();
+        mSummaryByGroupKey.clear();
+    }
+
+    @VisibleForTesting
+    void addNotification(NotificationRecord r) {
+        mNotificationList.add(r);
+        mNotificationsByKey.put(r.sbn.getKey(), r);
+        if (r.sbn.isGroup()) {
+            mSummaryByGroupKey.put(r.getGroupKey(), r);
+        }
+    }
+
+    @VisibleForTesting
+    void addEnqueuedNotification(NotificationRecord r) {
+        mEnqueuedNotifications.add(r);
+    }
+
+    @VisibleForTesting
+    NotificationRecord getNotificationRecord(String key) {
+        return mNotificationsByKey.get(key);
+    }
+
+
+    @VisibleForTesting
     void setSystemReady(boolean systemReady) {
         mSystemReady = systemReady;
     }
 
     @VisibleForTesting
-    void setHandler(Handler handler) {
+    void setHandler(WorkerHandler handler) {
         mHandler = handler;
     }
 
     @VisibleForTesting
-    void setSystemNotificationSound(String systemNotificationSound) {
-        mSystemNotificationSound = systemNotificationSound;
+    void setFallbackVibrationPattern(long[] vibrationPattern) {
+        mFallbackVibrationPattern = vibrationPattern;
     }
 
-    @Override
-    public void onStart() {
-        Resources resources = getContext().getResources();
+    @VisibleForTesting
+    void setPackageManager(IPackageManager packageManager) {
+        mPackageManager = packageManager;
+    }
 
+    @VisibleForTesting
+    void setRankingHelper(RankingHelper rankingHelper) {
+        mRankingHelper = rankingHelper;
+    }
+
+    @VisibleForTesting
+    void setRankingHandler(RankingHandler rankingHandler) {
+        mRankingHandler = rankingHandler;
+    }
+
+    @VisibleForTesting
+    void setIsTelevision(boolean isTelevision) {
+        mIsTelevision = isTelevision;
+    }
+
+    @VisibleForTesting
+    void setUsageStats(NotificationUsageStats us) {
+        mUsageStats = us;
+    }
+
+    @VisibleForTesting
+    void setAccessibilityManager(AccessibilityManager am) {
+        mAccessibilityManager = am;
+    }
+
+    // TODO: All tests should use this init instead of the one-off setters above.
+    @VisibleForTesting
+    void init(Looper looper, IPackageManager packageManager,
+            PackageManager packageManagerClient,
+            LightsManager lightsManager, NotificationListeners notificationListeners,
+            NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
+            ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
+            NotificationUsageStats usageStats, AtomicFile policyFile,
+            ActivityManager activityManager, GroupHelper groupHelper) {
+        Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
                 DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE);
 
-        mAm = ActivityManagerNative.getDefault();
+        mAccessibilityManager =
+                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        mAm = ActivityManager.getService();
+        mPackageManager = packageManager;
+        mPackageManagerClient = packageManagerClient;
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
         mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+        mCompanionManager = companionManager;
+        mActivityManager = activityManager;
 
-        // This is the package that contains the AOSP framework update.
-        mRankerServicePackageName = getContext().getPackageManager()
-                .getServicesSystemSharedLibraryPackageName();
-
-        mHandler = new WorkerHandler();
+        mHandler = new WorkerHandler(looper);
         mRankingThread.start();
         String[] extractorNames;
         try {
@@ -944,13 +1230,14 @@ public class NotificationManagerService extends SystemService {
         } catch (Resources.NotFoundException e) {
             extractorNames = new String[0];
         }
-        mUsageStats = new NotificationUsageStats(getContext());
+        mUsageStats = usageStats;
         mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
         mRankingHelper = new RankingHelper(getContext(),
+                mPackageManagerClient,
                 mRankingHandler,
                 mUsageStats,
                 extractorNames);
-        mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+        mConditionProviders = conditionProviders;
         mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
@@ -965,7 +1252,7 @@ public class NotificationManagerService extends SystemService {
                         new Intent(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL)
                                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
                         UserHandle.ALL, android.Manifest.permission.MANAGE_NOTIFICATIONS);
-                synchronized(mNotificationList) {
+                synchronized (mNotificationLock) {
                     updateInterruptionFilterLocked();
                 }
             }
@@ -975,44 +1262,37 @@ public class NotificationManagerService extends SystemService {
                 sendRegisteredOnlyBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED);
             }
         });
-        final File systemDir = new File(Environment.getDataDirectory(), "system");
-        mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
+        mSnoozeHelper = snoozeHelper;
+        mGroupHelper = groupHelper;
 
-        syncBlockDb();
+        // This is a ManagedServices object that keeps track of the listeners.
+        mListeners = notificationListeners;
 
-        // This is a MangedServices object that keeps track of the listeners.
-        mListeners = new NotificationListeners();
+        // This is a MangedServices object that keeps track of the assistant.
+        mAssistants = notificationAssistants;
 
-        // This is a MangedServices object that keeps track of the ranker.
-        mRankerServices = new NotificationRankers();
-        // Find the updatable ranker and register it.
-        mRankerServices.registerRanker();
+        mPolicyFile = policyFile;
+        loadPolicyFile();
 
         mStatusBar = getLocalService(StatusBarManagerInternal.class);
         if (mStatusBar != null) {
             mStatusBar.setNotificationDelegate(mNotificationDelegate);
         }
 
-        final LightsManager lights = getLocalService(LightsManager.class);
-        mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
-        mAttentionLight = lights.getLight(LightsManager.LIGHT_ID_ATTENTION);
-
-        mDefaultNotificationColor = resources.getColor(
-                R.color.config_defaultNotificationColor);
-        mDefaultNotificationLedOn = resources.getInteger(
-                R.integer.config_defaultNotificationLedOn);
-        mDefaultNotificationLedOff = resources.getInteger(
-                R.integer.config_defaultNotificationLedOff);
-
-        mDefaultVibrationPattern = getLongArray(resources,
-                R.array.config_defaultNotificationVibePattern,
-                VIBRATE_PATTERN_MAXLEN,
-                DEFAULT_VIBRATE_PATTERN);
+        mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
+        mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
         mFallbackVibrationPattern = getLongArray(resources,
                 R.array.config_notificationFallbackVibePattern,
                 VIBRATE_PATTERN_MAXLEN,
                 DEFAULT_VIBRATE_PATTERN);
+        mInCallNotificationUri = Uri.parse("file://" +
+                resources.getString(R.string.config_inCallNotificationSound));
+        mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                .build();
+        mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
         mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight);
 
@@ -1030,6 +1310,46 @@ public class NotificationManagerService extends SystemService {
         mUserProfiles.updateCache(getContext());
         listenForCallState();
 
+        mSettingsObserver = new SettingsObserver(mHandler);
+
+        mArchive = new Archive(resources.getInteger(
+                R.integer.config_notificationServiceArchiveSize));
+
+        mIsTelevision = mPackageManagerClient.hasSystemFeature(FEATURE_LEANBACK)
+                || mPackageManagerClient.hasSystemFeature(FEATURE_TELEVISION);
+    }
+
+    @Override
+    public void onStart() {
+        SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), new SnoozeHelper.Callback() {
+            @Override
+            public void repost(int userId, NotificationRecord r) {
+                try {
+                    if (DBG) {
+                        Slog.d(TAG, "Reposting " + r.getKey());
+                    }
+                    enqueueNotificationInternal(r.sbn.getPackageName(), r.sbn.getOpPkg(),
+                            r.sbn.getUid(), r.sbn.getInitialPid(), r.sbn.getTag(), r.sbn.getId(),
+                            r.sbn.getNotification(), userId);
+                } catch (Exception e) {
+                    Slog.e(TAG, "Cannot un-snooze notification", e);
+                }
+            }
+        }, mUserProfiles);
+
+        final File systemDir = new File(Environment.getDataDirectory(), "system");
+
+        init(Looper.myLooper(),
+                AppGlobals.getPackageManager(), getContext().getPackageManager(),
+                getLocalService(LightsManager.class),
+                new NotificationListeners(AppGlobals.getPackageManager()),
+                new NotificationAssistants(AppGlobals.getPackageManager()),
+                new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
+                null, snoozeHelper, new NotificationUsageStats(getContext()),
+                new AtomicFile(new File(systemDir, "notification_policy.xml")),
+                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
+                getGroupHelper());
+
         // register for various Intents
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_ON);
@@ -1063,58 +1383,53 @@ public class NotificationManagerService extends SystemService {
         getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
                 null);
 
-        mSettingsObserver = new SettingsObserver(mHandler);
+        IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+        timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+        getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
 
-        mArchive = new Archive(resources.getInteger(
-                R.integer.config_notificationServiceArchiveSize));
+        IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
+        getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
+
+        IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
+        getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
 
         publishBinderService(Context.NOTIFICATION_SERVICE, mService);
         publishLocalService(NotificationManagerInternal.class, mInternalService);
     }
 
-    private void sendRegisteredOnlyBroadcast(String action) {
-        getContext().sendBroadcastAsUser(new Intent(action)
-                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL, null);
-    }
+    private GroupHelper getGroupHelper() {
+        return new GroupHelper(new GroupHelper.Callback() {
+            @Override
+            public void addAutoGroup(String key) {
+                synchronized (mNotificationLock) {
+                    addAutogroupKeyLocked(key);
+                }
+            }
 
-    /**
-     * Make sure the XML config and the the AppOps system agree about blocks.
-     */
-    private void syncBlockDb() {
-        loadPolicyFile();
+            @Override
+            public void removeAutoGroup(String key) {
+                synchronized (mNotificationLock) {
+                    removeAutogroupKeyLocked(key);
+                }
+            }
 
-        // sync bans from ranker into app opps
-        Map<Integer, String> packageBans = mRankingHelper.getPackageBans();
-        for(Entry<Integer, String> ban : packageBans.entrySet()) {
-            final int uid = ban.getKey();
-            final String packageName = ban.getValue();
-            setNotificationsEnabledForPackageImpl(packageName, uid, false);
-        }
-
-        // sync bans from app opps into ranker
-        packageBans.clear();
-        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
-            final int userId = user.getUserHandle().getIdentifier();
-            final PackageManager packageManager = getContext().getPackageManager();
-            List<PackageInfo> packages = packageManager.getInstalledPackagesAsUser(0, userId);
-            final int packageCount = packages.size();
-            for (int p = 0; p < packageCount; p++) {
-                final String packageName = packages.get(p).packageName;
-                try {
-                    final int uid = packageManager.getPackageUidAsUser(packageName, userId);
-                    if (!checkNotificationOp(packageName, uid)) {
-                        packageBans.put(uid, packageName);
-                    }
-                } catch (NameNotFoundException e) {
-                    // forget you
+            @Override
+            public void addAutoGroupSummary(int userId, String pkg, String triggeringKey) {
+                createAutoGroupSummary(userId, pkg, triggeringKey);
+            }
+
+            @Override
+            public void removeAutoGroupSummary(int userId, String pkg) {
+                synchronized (mNotificationLock) {
+                    clearAutogroupSummaryLocked(userId, pkg);
                 }
             }
-        }
-        for (Entry<Integer, String> ban : packageBans.entrySet()) {
-            mRankingHelper.setImportance(ban.getValue(), ban.getKey(), IMPORTANCE_NONE);
-        }
+        });
+    }
 
-        savePolicyFile();
+    private void sendRegisteredOnlyBroadcast(String action) {
+        getContext().sendBroadcastAsUser(new Intent(action)
+                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL, null);
     }
 
     @Override
@@ -1133,24 +1448,12 @@ public class NotificationManagerService extends SystemService {
             // bind to listener services.
             mSettingsObserver.observe();
             mListeners.onBootPhaseAppsCanStart();
-            mRankerServices.onBootPhaseAppsCanStart();
+            mAssistants.onBootPhaseAppsCanStart();
             mConditionProviders.onBootPhaseAppsCanStart();
         }
     }
 
-    void setNotificationsEnabledForPackageImpl(String pkg, int uid, boolean enabled) {
-        Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
-
-        mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
-                enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
-
-        // Now, cancel any outstanding notifications that are part of a just-disabled app
-        if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, 0, 0, true, UserHandle.getUserId(uid),
-                    REASON_PACKAGE_BANNED, null);
-        }
-    }
-
+    @GuardedBy("mNotificationLock")
     private void updateListenerHintsLocked() {
         final int hints = calculateHints();
         if (hints == mListenerHints) return;
@@ -1159,6 +1462,7 @@ public class NotificationManagerService extends SystemService {
         scheduleListenerHintsChanged(hints);
     }
 
+    @GuardedBy("mNotificationLock")
     private void updateEffectsSuppressorLocked() {
         final long updatedSuppressedEffects = calculateSuppressedEffects();
         if (updatedSuppressedEffects == mZenModeHelper.getSuppressedEffects()) return;
@@ -1169,6 +1473,37 @@ public class NotificationManagerService extends SystemService {
         sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
     }
 
+    private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
+            boolean fromListener) {
+        if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+            // cancel
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+                    UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
+                    null);
+            if (isUidSystemOrPhone(uid)) {
+                int[] profileIds = mUserProfiles.getCurrentProfileIds();
+                int N = profileIds.length;
+                for (int i = 0; i < N; i++) {
+                    int profileId = profileIds[i];
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+                            profileId, REASON_CHANNEL_BANNED,
+                            null);
+                }
+            }
+        }
+        mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+
+        if (!fromListener) {
+            final NotificationChannel modifiedChannel =
+                    mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+            mListeners.notifyNotificationChannelChanged(
+                    pkg, UserHandle.getUserHandleForUid(uid),
+                    modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+        }
+
+        savePolicyFile();
+    }
+
     private ArrayList<ComponentName> getSuppressors() {
         ArrayList<ComponentName> names = new ArrayList<ComponentName>();
         for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1258,6 +1593,7 @@ public class NotificationManagerService extends SystemService {
         return suppressedEffects;
     }
 
+    @GuardedBy("mNotificationLock")
     private void updateInterruptionFilterLocked() {
         int interruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
         if (interruptionFilter == mInterruptionFilter) return;
@@ -1265,6 +1601,16 @@ public class NotificationManagerService extends SystemService {
         scheduleInterruptionFilterChanged(interruptionFilter);
     }
 
+    @VisibleForTesting
+    INotificationManager getBinderService() {
+        return INotificationManager.Stub.asInterface(mService);
+    }
+
+    @VisibleForTesting
+    NotificationManagerInternal getInternalService() {
+        return mInternalService;
+    }
+
     private final IBinder mService = new INotificationManager.Stub() {
         // Toasts
         // ============================================================================
@@ -1281,20 +1627,18 @@ public class NotificationManagerService extends SystemService {
                 Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                 return ;
             }
-
-            final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
+            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
             final boolean isPackageSuspended =
                     isPackageSuspendedForUser(pkg, Binder.getCallingUid());
 
-            if (ENABLE_BLOCKED_TOASTS && (!noteNotificationOp(pkg, Binder.getCallingUid())
-                    || isPackageSuspended)) {
-                if (!isSystemToast) {
-                    Slog.e(TAG, "Suppressing toast from package " + pkg
-                            + (isPackageSuspended
-                                    ? " due to package suspended by administrator."
-                                    : " by user request."));
-                    return;
-                }
+            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
+                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
+                            || isPackageSuspended)) {
+                Slog.e(TAG, "Suppressing toast from package " + pkg
+                        + (isPackageSuspended
+                                ? " due to package suspended by administrator."
+                                : " by user request."));
+                return;
             }
 
             synchronized (mToastQueue) {
@@ -1302,39 +1646,28 @@ public class NotificationManagerService extends SystemService {
                 long callingId = Binder.clearCallingIdentity();
                 try {
                     ToastRecord record;
-                    int index = indexOfToastLocked(pkg, callback);
-                    // If it's already in the queue, we update it in place, we don't
-                    // move it to the end of the queue.
+                    int index;
+                    // All packages aside from the android package can enqueue one toast at a time
+                    if (!isSystemToast) {
+                        index = indexOfToastPackageLocked(pkg);
+                    } else {
+                        index = indexOfToastLocked(pkg, callback);
+                    }
+
+                    // If the package already has a toast, we update its toast
+                    // in the queue, we don't move it to the end of the queue.
                     if (index >= 0) {
                         record = mToastQueue.get(index);
                         record.update(duration);
+                        record.update(callback);
                     } else {
-                        // Limit the number of toasts that any given package except the android
-                        // package can enqueue.  Prevents DOS attacks and deals with leaks.
-                        if (!isSystemToast) {
-                            int count = 0;
-                            final int N = mToastQueue.size();
-                            for (int i=0; i<N; i++) {
-                                 final ToastRecord r = mToastQueue.get(i);
-                                 if (r.pkg.equals(pkg)) {
-                                     count++;
-                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
-                                         Slog.e(TAG, "Package has already posted " + count
-                                                + " toasts. Not showing more. Package=" + pkg);
-                                         return;
-                                     }
-                                 }
-                            }
-                        }
-
                         Binder token = new Binder();
-                        mWindowManagerInternal.addWindowToken(token,
-                                WindowManager.LayoutParams.TYPE_TOAST);
+                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                         record = new ToastRecord(callingPid, pkg, callback, duration, token);
                         mToastQueue.add(record);
                         index = mToastQueue.size() - 1;
-                        keepProcessAliveIfNeededLocked(callingPid);
                     }
+                    keepProcessAliveIfNeededLocked(callingPid);
                     // If it's at index 0, it's the current toast.  It doesn't matter if it's
                     // new or just been updated.  Call back and tell it to show itself.
                     // If the callback fails, this will remove it from the list, so don't
@@ -1375,9 +1708,9 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
-                Notification notification, int[] idOut, int userId) throws RemoteException {
+                Notification notification, int userId) throws RemoteException {
             enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
-                    Binder.getCallingPid(), tag, id, notification, idOut, userId);
+                    Binder.getCallingPid(), tag, id, notification, userId);
         }
 
         @Override
@@ -1387,12 +1720,10 @@ public class NotificationManagerService extends SystemService {
                     Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
             // Don't allow client applications to cancel foreground service notis or autobundled
             // summaries.
+            final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
+                    (Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_AUTOGROUP_SUMMARY);
             cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
-                    (Binder.getCallingUid() == Process.SYSTEM_UID
-                            ? 0 : Notification.FLAG_FOREGROUND_SERVICE)
-                            | (Binder.getCallingUid() == Process.SYSTEM_UID
-                            ? 0 : Notification.FLAG_AUTOGROUP_SUMMARY), false, userId,
-                    REASON_APP_CANCEL, null);
+                    mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null);
         }
 
         @Override
@@ -1405,7 +1736,7 @@ public class NotificationManagerService extends SystemService {
             // Calling from user space, don't allow the canceling of actively
             // running foreground services.
             cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
-                    pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
+                    pkg, null, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
                     REASON_APP_CANCEL_ALL, null);
         }
 
@@ -1413,8 +1744,12 @@ public class NotificationManagerService extends SystemService {
         public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
             checkCallerIsSystem();
 
-            setNotificationsEnabledForPackageImpl(pkg, uid, enabled);
             mRankingHelper.setEnabled(pkg, uid, enabled);
+            // Now, cancel any outstanding notifications that are part of a just-disabled app
+            if (!enabled) {
+                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
+                        UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
+            }
             savePolicyFile();
         }
 
@@ -1432,77 +1767,254 @@ public class NotificationManagerService extends SystemService {
         @Override
         public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
             checkCallerIsSystemOrSameApp(pkg);
-            return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
-                    == AppOpsManager.MODE_ALLOWED) && !isPackageSuspendedForUser(pkg, uid);
+            if (UserHandle.getCallingUserId() != UserHandle.getUserId(uid)) {
+                getContext().enforceCallingPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        "canNotifyAsPackage for uid " + uid);
+            }
+
+            return mRankingHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
         }
 
         @Override
-        public void setPriority(String pkg, int uid, int priority) {
-            checkCallerIsSystem();
-            mRankingHelper.setPriority(pkg, uid, priority);
-            savePolicyFile();
+        public int getPackageImportance(String pkg) {
+            checkCallerIsSystemOrSameApp(pkg);
+            return mRankingHelper.getImportance(pkg, Binder.getCallingUid());
         }
 
         @Override
-        public int getPriority(String pkg, int uid) {
+        public boolean canShowBadge(String pkg, int uid) {
             checkCallerIsSystem();
-            return mRankingHelper.getPriority(pkg, uid);
+            return mRankingHelper.canShowBadge(pkg, uid);
         }
 
         @Override
-        public void setVisibilityOverride(String pkg, int uid, int visibility) {
+        public void setShowBadge(String pkg, int uid, boolean showBadge) {
             checkCallerIsSystem();
-            mRankingHelper.setVisibilityOverride(pkg, uid, visibility);
+            mRankingHelper.setShowBadge(pkg, uid, showBadge);
             savePolicyFile();
         }
 
         @Override
-        public int getVisibilityOverride(String pkg, int uid) {
-            checkCallerIsSystem();
-            return mRankingHelper.getVisibilityOverride(pkg, uid);
+        public void createNotificationChannelGroups(String pkg,
+                ParceledListSlice channelGroupList) throws RemoteException {
+            checkCallerIsSystemOrSameApp(pkg);
+            List<NotificationChannelGroup> groups = channelGroupList.getList();
+            final int groupSize = groups.size();
+            for (int i = 0; i < groupSize; i++) {
+                final NotificationChannelGroup group = groups.get(i);
+                Preconditions.checkNotNull(group, "group in list is null");
+                mRankingHelper.createNotificationChannelGroup(pkg, Binder.getCallingUid(), group,
+                        true /* fromTargetApp */);
+                mListeners.notifyNotificationChannelGroupChanged(pkg,
+                        UserHandle.of(UserHandle.getCallingUserId()), group,
+                        NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+            }
+            savePolicyFile();
         }
 
-        @Override
-        public void setImportance(String pkg, int uid, int importance) {
-            enforceSystemOrSystemUI("Caller not system or systemui");
-            setNotificationsEnabledForPackageImpl(pkg, uid,
-                    importance != NotificationListenerService.Ranking.IMPORTANCE_NONE);
-            mRankingHelper.setImportance(pkg, uid, importance);
+        private void createNotificationChannelsImpl(String pkg, int uid,
+                ParceledListSlice channelsList) {
+            List<NotificationChannel> channels = channelsList.getList();
+            final int channelsSize = channels.size();
+            for (int i = 0; i < channelsSize; i++) {
+                final NotificationChannel channel = channels.get(i);
+                Preconditions.checkNotNull(channel, "channel in list is null");
+                mRankingHelper.createNotificationChannel(pkg, uid, channel,
+                        true /* fromTargetApp */);
+                mListeners.notifyNotificationChannelChanged(pkg,
+                        UserHandle.getUserHandleForUid(uid),
+                        mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
+                        NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+            }
             savePolicyFile();
         }
 
         @Override
-        public int getPackageImportance(String pkg) {
+        public void createNotificationChannels(String pkg,
+                ParceledListSlice channelsList) throws RemoteException {
             checkCallerIsSystemOrSameApp(pkg);
-            return mRankingHelper.getImportance(pkg, Binder.getCallingUid());
+            createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
         }
 
         @Override
-        public int getImportance(String pkg, int uid) {
-            enforceSystemOrSystemUI("Caller not system or systemui");
-            return mRankingHelper.getImportance(pkg, uid);
+        public void createNotificationChannelsForPackage(String pkg, int uid,
+                ParceledListSlice channelsList) throws RemoteException {
+            checkCallerIsSystem();
+            createNotificationChannelsImpl(pkg, uid, channelsList);
         }
 
-        /**
-         * System-only API for getting a list of current (i.e. not cleared) notifications.
-         *
-         * Requires ACCESS_NOTIFICATIONS which is signature|system.
-         * @returns A list of all the notifications, in natural order.
-         */
         @Override
-        public StatusBarNotification[] getActiveNotifications(String callingPkg) {
-            // enforce() will ensure the calling uid has the correct permission
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ACCESS_NOTIFICATIONS,
-                    "NotificationManagerService.getActiveNotifications");
+        public NotificationChannel getNotificationChannel(String pkg, String channelId) {
+            checkCallerIsSystemOrSameApp(pkg);
+            return mRankingHelper.getNotificationChannel(
+                    pkg, Binder.getCallingUid(), channelId, false /* includeDeleted */);
+        }
 
-            StatusBarNotification[] tmp = null;
-            int uid = Binder.getCallingUid();
+        @Override
+        public NotificationChannel getNotificationChannelForPackage(String pkg, int uid,
+                String channelId, boolean includeDeleted) {
+            checkCallerIsSystem();
+            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
+        }
 
-            // noteOp will check to make sure the callingPkg matches the uid
+        @Override
+        public void deleteNotificationChannel(String pkg, String channelId) {
+            checkCallerIsSystemOrSameApp(pkg);
+            final int callingUid = Binder.getCallingUid();
+            if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
+                throw new IllegalArgumentException("Cannot delete default channel");
+            }
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
+                    UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+            mRankingHelper.deleteNotificationChannel(pkg, callingUid, channelId);
+            mListeners.notifyNotificationChannelChanged(pkg,
+                    UserHandle.getUserHandleForUid(callingUid),
+                    mRankingHelper.getNotificationChannel(pkg, callingUid, channelId, true),
+                    NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+            savePolicyFile();
+        }
+
+        @Override
+        public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
+                String pkg) {
+            checkCallerIsSystemOrSameApp(pkg);
+            return new ParceledListSlice<>(new ArrayList(
+                    mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid())));
+        }
+
+        @Override
+        public void deleteNotificationChannelGroup(String pkg, String groupId) {
+            checkCallerIsSystemOrSameApp(pkg);
+
+            final int callingUid = Binder.getCallingUid();
+            NotificationChannelGroup groupToDelete =
+                    mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
+            if (groupToDelete != null) {
+                List<NotificationChannel> deletedChannels =
+                        mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
+                for (int i = 0; i < deletedChannels.size(); i++) {
+                    final NotificationChannel deletedChannel = deletedChannels.get(i);
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
+                            true,
+                            UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+                            null);
+                    mListeners.notifyNotificationChannelChanged(pkg,
+                            UserHandle.getUserHandleForUid(callingUid),
+                            deletedChannel,
+                            NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+                }
+                mListeners.notifyNotificationChannelGroupChanged(
+                        pkg, UserHandle.getUserHandleForUid(callingUid), groupToDelete,
+                        NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+                savePolicyFile();
+            }
+        }
+
+        @Override
+        public void updateNotificationChannelForPackage(String pkg, int uid,
+                NotificationChannel channel) {
+            enforceSystemOrSystemUI("Caller not system or systemui");
+            Preconditions.checkNotNull(channel);
+            updateNotificationChannelInt(pkg, uid, channel, false);
+        }
+
+        @Override
+        public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg,
+                int uid, boolean includeDeleted) {
+            enforceSystemOrSystemUI("getNotificationChannelsForPackage");
+            return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted);
+        }
+
+        @Override
+        public int getNumNotificationChannelsForPackage(String pkg, int uid,
+                boolean includeDeleted) {
+            enforceSystemOrSystemUI("getNumNotificationChannelsForPackage");
+            return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted)
+                    .getList().size();
+        }
+
+        @Override
+        public boolean onlyHasDefaultChannel(String pkg, int uid) {
+            enforceSystemOrSystemUI("onlyHasDefaultChannel");
+            return mRankingHelper.onlyHasDefaultChannel(pkg, uid);
+        }
+
+        @Override
+        public int getDeletedChannelCount(String pkg, int uid) {
+            enforceSystemOrSystemUI("getDeletedChannelCount");
+            return mRankingHelper.getDeletedChannelCount(pkg, uid);
+        }
+
+        @Override
+        public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
+                String pkg, int uid, boolean includeDeleted) {
+            checkCallerIsSystem();
+            return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
+        }
+
+        @Override
+        public NotificationChannelGroup getNotificationChannelGroupForPackage(
+                String groupId, String pkg, int uid) {
+            enforceSystemOrSystemUI("getNotificationChannelGroupForPackage");
+            return mRankingHelper.getNotificationChannelGroup(groupId, pkg, uid);
+        }
+
+        @Override
+        public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg) {
+            checkCallerIsSystemOrSameApp(pkg);
+            return mRankingHelper.getNotificationChannels(
+                    pkg, Binder.getCallingUid(), false /* includeDeleted */);
+        }
+
+        @Override
+        public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException {
+            checkCallerIsSystem();
+
+            // Cancel posted notifications
+            cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
+                    UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+
+            final String[] packages = new String[] {packageName};
+            final int[] uids = new int[] {uid};
+
+            // Listener & assistant
+            mListeners.onPackagesChanged(true, packages, uids);
+            mAssistants.onPackagesChanged(true, packages, uids);
+
+            // Zen
+            mConditionProviders.onPackagesChanged(true, packages, uids);
+
+            // Reset notification preferences
+            if (!fromApp) {
+                mRankingHelper.onPackagesChanged(
+                        true, UserHandle.getCallingUserId(), packages, uids);
+            }
+
+            savePolicyFile();
+        }
+
+
+        /**
+         * System-only API for getting a list of current (i.e. not cleared) notifications.
+         *
+         * Requires ACCESS_NOTIFICATIONS which is signature|system.
+         * @returns A list of all the notifications, in natural order.
+         */
+        @Override
+        public StatusBarNotification[] getActiveNotifications(String callingPkg) {
+            // enforce() will ensure the calling uid has the correct permission
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_NOTIFICATIONS,
+                    "NotificationManagerService.getActiveNotifications");
+
+            StatusBarNotification[] tmp = null;
+            int uid = Binder.getCallingUid();
+
+            // noteOp will check to make sure the callingPkg matches the uid
             if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
                     == AppOpsManager.MODE_ALLOWED) {
-                synchronized (mNotificationList) {
+                synchronized (mNotificationLock) {
                     tmp = new StatusBarNotification[mNotificationList.size()];
                     final int N = mNotificationList.size();
                     for (int i=0; i<N; i++) {
@@ -1516,6 +2028,9 @@ public class NotificationManagerService extends SystemService {
         /**
          * Public API for getting a list of current notifications for the calling package/uid.
          *
+         * Note that since notification posting is done asynchronously, this will not return
+         * notifications that are in the process of being posted.
+         *
          * @returns A list of all the package's notifications, in natural order.
          */
         @Override
@@ -1525,33 +2040,51 @@ public class NotificationManagerService extends SystemService {
             int userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), incomingUserId, true, false,
                     "getAppActiveNotifications", pkg);
-
-            final ArrayList<StatusBarNotification> list
-                    = new ArrayList<StatusBarNotification>(mNotificationList.size());
-
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
+                final ArrayMap<String, StatusBarNotification> map
+                        = new ArrayMap<>(mNotificationList.size() + mEnqueuedNotifications.size());
                 final int N = mNotificationList.size();
                 for (int i = 0; i < N; i++) {
-                    final StatusBarNotification sbn = mNotificationList.get(i).sbn;
-                    if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId
-                            && (sbn.getNotification().flags
-                            & Notification.FLAG_AUTOGROUP_SUMMARY) == 0) {
-                        // We could pass back a cloneLight() but clients might get confused and
-                        // try to send this thing back to notify() again, which would not work
-                        // very well.
-                        final StatusBarNotification sbnOut = new StatusBarNotification(
-                                sbn.getPackageName(),
-                                sbn.getOpPkg(),
-                                sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                                0, // hide score from apps
-                                sbn.getNotification().clone(),
-                                sbn.getUser(), sbn.getPostTime());
-                        list.add(sbnOut);
+                    StatusBarNotification sbn = sanitizeSbn(pkg, userId,
+                            mNotificationList.get(i).sbn);
+                    if (sbn != null) {
+                        map.put(sbn.getKey(), sbn);
+                    }
+                }
+                for(NotificationRecord snoozed: mSnoozeHelper.getSnoozed(userId, pkg)) {
+                    StatusBarNotification sbn = sanitizeSbn(pkg, userId, snoozed.sbn);
+                    if (sbn != null) {
+                        map.put(sbn.getKey(), sbn);
+                    }
+                }
+                final int M = mEnqueuedNotifications.size();
+                for (int i = 0; i < M; i++) {
+                    StatusBarNotification sbn = sanitizeSbn(pkg, userId,
+                            mEnqueuedNotifications.get(i).sbn);
+                    if (sbn != null) {
+                        map.put(sbn.getKey(), sbn); // pending update overwrites existing post here
                     }
                 }
+                final ArrayList<StatusBarNotification> list = new ArrayList<>(map.size());
+                list.addAll(map.values());
+                return new ParceledListSlice<StatusBarNotification>(list);
             }
+        }
 
-            return new ParceledListSlice<StatusBarNotification>(list);
+        private StatusBarNotification sanitizeSbn(String pkg, int userId,
+                StatusBarNotification sbn) {
+            if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) {
+                // We could pass back a cloneLight() but clients might get confused and
+                // try to send this thing back to notify() again, which would not work
+                // very well.
+                return new StatusBarNotification(
+                        sbn.getPackageName(),
+                        sbn.getOpPkg(),
+                        sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+                        sbn.getNotification().clone(),
+                        sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
+            }
+            return null;
         }
 
         /**
@@ -1613,7 +2146,7 @@ public class NotificationManagerService extends SystemService {
             final int callingPid = Binder.getCallingPid();
             long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
+                synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                     if (keys != null) {
                         final int N = keys.length;
@@ -1651,8 +2184,8 @@ public class NotificationManagerService extends SystemService {
             long identity = Binder.clearCallingIdentity();
             try {
                 ManagedServices manager =
-                        mRankerServices.isComponentEnabledForCurrentProfiles(component)
-                        ? mRankerServices
+                        mAssistants.isComponentEnabledForCurrentProfiles(component)
+                        ? mAssistants
                         : mListeners;
                 manager.setComponentState(component, true);
             } finally {
@@ -1665,8 +2198,10 @@ public class NotificationManagerService extends SystemService {
             long identity = Binder.clearCallingIdentity();
             try {
                 // allow bound services to disable themselves
-                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                info.getOwner().setComponentState(info.component, false);
+                synchronized (mNotificationLock) {
+                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                    info.getOwner().setComponentState(info.component, false);
+                }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -1676,7 +2211,7 @@ public class NotificationManagerService extends SystemService {
         public void setNotificationsShownFromListener(INotificationListener token, String[] keys) {
             long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
+                synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                     if (keys != null) {
                         final int N = keys.length;
@@ -1705,6 +2240,14 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
+        /**
+         * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
+         *
+         * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
+         *
+         * @param info The binder for the listener, to check that the caller is allowed
+         */
+        @GuardedBy("mNotificationLock")
         private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
                 int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
@@ -1714,6 +2257,63 @@ public class NotificationManagerService extends SystemService {
         }
 
         /**
+         * Allow an INotificationListener to snooze a single notification until a context.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public void snoozeNotificationUntilContextFromListener(INotificationListener token,
+                String key, String snoozeCriterionId) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mNotificationLock) {
+                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                    snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
+         * Allow an INotificationListener to snooze a single notification until a time.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public void snoozeNotificationUntilFromListener(INotificationListener token, String key,
+                long duration) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mNotificationLock) {
+                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                    snoozeNotificationInt(key, duration, null, info);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
+         * Allows the notification assistant to un-snooze a single notification.
+         *
+         * @param token The binder for the assistant, to check that the caller is allowed
+         */
+        @Override
+        public void unsnoozeNotificationFromAssistant(INotificationListener token, String key) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mNotificationLock) {
+                    final ManagedServiceInfo info =
+                            mAssistants.checkServiceTokenLocked(token);
+                    unsnoozeNotificationInt(key, info);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
          * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
          *
          * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
@@ -1727,7 +2327,7 @@ public class NotificationManagerService extends SystemService {
             final int callingPid = Binder.getCallingPid();
             long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
+                synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                     if (info.supportsProfiles()) {
                         Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
@@ -1756,7 +2356,7 @@ public class NotificationManagerService extends SystemService {
         @Override
         public ParceledListSlice<StatusBarNotification> getActiveNotificationsFromListener(
                 INotificationListener token, String[] keys, int trim) {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                 final boolean getKeys = keys != null;
                 final int N = getKeys ? keys.length : mNotificationList.size();
@@ -1777,11 +2377,41 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
+        /**
+         * Allow an INotificationListener to request the list of outstanding snoozed notifications
+         * seen by the current user. Useful when starting up, after which point the listener
+         * callbacks should be used.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         * @returns The return value will contain the notifications specified in keys, in that
+         *      order, or if keys is null, all the notifications, in natural order.
+         */
+        @Override
+        public ParceledListSlice<StatusBarNotification> getSnoozedNotificationsFromListener(
+                INotificationListener token, int trim) {
+            synchronized (mNotificationLock) {
+                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                List<NotificationRecord> snoozedRecords = mSnoozeHelper.getSnoozed();
+                final int N = snoozedRecords.size();
+                final ArrayList<StatusBarNotification> list = new ArrayList<>(N);
+                for (int i=0; i < N; i++) {
+                    final NotificationRecord r = snoozedRecords.get(i);
+                    if (r == null) continue;
+                    StatusBarNotification sbn = r.sbn;
+                    if (!isVisibleToListener(sbn, info)) continue;
+                    StatusBarNotification sbnToSend =
+                            (trim == TRIM_FULL) ? sbn : sbn.cloneLight();
+                    list.add(sbnToSend);
+                }
+                return new ParceledListSlice<>(list);
+            }
+        }
+
         @Override
         public void requestHintsFromListener(INotificationListener token, int hints) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
+                synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                     final int disableEffectsMask = HINT_HOST_DISABLE_EFFECTS
                             | HINT_HOST_DISABLE_NOTIFICATION_EFFECTS
@@ -1802,7 +2432,7 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public int getHintsFromListener(INotificationListener token) {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 return mListenerHints;
             }
         }
@@ -1812,7 +2442,7 @@ public class NotificationManagerService extends SystemService {
                 int interruptionFilter) throws RemoteException {
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
+                synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                     mZenModeHelper.requestFromListener(info.component, interruptionFilter);
                     updateInterruptionFilterLocked();
@@ -1833,7 +2463,7 @@ public class NotificationManagerService extends SystemService {
         @Override
         public void setOnNotificationPostedTrimFromListener(INotificationListener token, int trim)
                 throws RemoteException {
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                 if (info == null) return;
                 mListeners.setOnNotificationPostedTrimLocked(info, trim);
@@ -1847,13 +2477,13 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public ZenModeConfig getZenModeConfig() {
-            enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig");
+            enforceSystemOrSystemUI("INotificationManager.getZenModeConfig");
             return mZenModeHelper.getConfig();
         }
 
         @Override
         public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
-            enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
+            enforceSystemOrSystemUI("INotificationManager.setZenMode");
             final long identity = Binder.clearCallingIdentity();
             try {
                 mZenModeHelper.setManualZenMode(mode, conditionId, null, reason);
@@ -1952,18 +2582,31 @@ public class NotificationManagerService extends SystemService {
             });
         }
 
-        private void enforceSystemOrSystemUIOrVolume(String message) {
-            if (mAudioManagerInternal != null) {
-                final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
-                if (vcuid > 0 && Binder.getCallingUid() == vcuid) {
-                    return;
-                }
+        @Override
+        public void requestUnbindProvider(IConditionProvider provider) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                // allow bound services to disable themselves
+                final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
+                info.getOwner().setComponentState(info.component, false);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void requestBindProvider(ComponentName component) {
+            checkCallerIsSystemOrSameApp(component.getPackageName());
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mConditionProviders.setComponentState(component, true);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
-            enforceSystemOrSystemUI(message);
         }
 
         private void enforceSystemOrSystemUI(String message) {
-            if (isCallerSystem()) return;
+            if (isCallerSystemOrPhone()) return;
             getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
                     message);
         }
@@ -1987,7 +2630,8 @@ public class NotificationManagerService extends SystemService {
             String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
             final int packageCount = packages.length;
             for (int i = 0; i < packageCount; i++) {
-                if (checkPolicyAccess(packages[i])) {
+                if (mConditionProviders.isPackageOrComponentAllowed(
+                        packages[i], UserHandle.getUserId(uid))) {
                     accessAllowed = true;
                 }
             }
@@ -2010,7 +2654,8 @@ public class NotificationManagerService extends SystemService {
         }
 
         private boolean checkPackagePolicyAccess(String pkg) {
-            return mPolicyAccess.isPackageGranted(pkg);
+            return mConditionProviders.isPackageOrComponentAllowed(
+                    pkg, getCallingUserHandle().getIdentifier());
         }
 
         private boolean checkPolicyAccess(String pkg) {
@@ -2030,17 +2675,12 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                    != PackageManager.PERMISSION_GRANTED) {
-                pw.println("Permission Denial: can't dump NotificationManager from pid="
-                        + Binder.getCallingPid()
-                        + ", uid=" + Binder.getCallingUid());
-                return;
-            }
-
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
             final DumpFilter filter = DumpFilter.parseFromArguments(args);
             if (filter != null && filter.stats) {
                 dumpJson(pw, filter);
+            } else if (filter != null && filter.proto) {
+                dumpProto(fd, filter);
             } else {
                 dumpImpl(pw, filter);
             }
@@ -2048,7 +2688,6 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public ComponentName getEffectsSuppressor() {
-            enforceSystemOrSystemUIOrVolume("INotificationManager.getEffectsSuppressor");
             return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null;
         }
 
@@ -2065,7 +2704,7 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public boolean isSystemConditionProviderEnabled(String path) {
-            enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled");
+            enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled");
             return mConditionProviders.isSystemProviderEnabled(path);
         }
 
@@ -2078,12 +2717,14 @@ public class NotificationManagerService extends SystemService {
                 Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
                 return null;
             }
-            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            try {
-                writePolicyXml(baos, true /*forBackup*/);
-                return baos.toByteArray();
-            } catch (IOException e) {
-                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+            synchronized(mPolicyFile) {
+                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                try {
+                    writePolicyXml(baos, true /*forBackup*/);
+                    return baos.toByteArray();
+                } catch (IOException e) {
+                    Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+                }
             }
             return null;
         }
@@ -2101,12 +2742,14 @@ public class NotificationManagerService extends SystemService {
                 Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
                 return;
             }
-            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
-            try {
-                readPolicyXml(bais, true /*forRestore*/);
-                savePolicyFile();
-            } catch (NumberFormatException | XmlPullParserException | IOException e) {
-                Slog.w(TAG, "applyRestore: error reading payload", e);
+            synchronized(mPolicyFile) {
+                final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+                try {
+                    readPolicyXml(bais, true /*forRestore*/);
+                    savePolicyFile();
+                } catch (NumberFormatException | XmlPullParserException | IOException e) {
+                    Slog.w(TAG, "applyRestore: error reading payload", e);
+                }
             }
         }
 
@@ -2123,25 +2766,28 @@ public class NotificationManagerService extends SystemService {
         }
 
         @Override
-        public String[] getPackagesRequestingNotificationPolicyAccess()
+        public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
                 throws RemoteException {
-            enforceSystemOrSystemUI("request policy access packages");
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                return mPolicyAccess.getRequestingPackages();
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
+            setNotificationPolicyAccessGrantedForUser(
+                    pkg, getCallingUserHandle().getIdentifier(), granted);
         }
 
         @Override
-        public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
-                throws RemoteException {
-            enforceSystemOrSystemUI("grant notification policy access");
+        public void setNotificationPolicyAccessGrantedForUser(
+                String pkg, int userId, boolean granted) {
+            checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
-                    mPolicyAccess.put(pkg, granted);
+                if (!mActivityManager.isLowRamDevice()) {
+                    mConditionProviders.setPackageOrComponentEnabled(
+                            pkg, userId, true, granted);
+
+                    getContext().sendBroadcastAsUser(new Intent(
+                            NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                                    .setPackage(pkg)
+                                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+                            UserHandle.of(userId), null);
+                    savePolicyFile();
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -2171,15 +2817,140 @@ public class NotificationManagerService extends SystemService {
         }
 
         @Override
-        public void applyAdjustmentFromRankerService(INotificationListener token,
+        public List<String> getEnabledNotificationListenerPackages() {
+            checkCallerIsSystem();
+            return mListeners.getAllowedPackages(getCallingUserHandle().getIdentifier());
+        }
+
+        @Override
+        public List<ComponentName> getEnabledNotificationListeners(int userId) {
+            checkCallerIsSystem();
+            return mListeners.getAllowedComponents(userId);
+        }
+
+        @Override
+        public boolean isNotificationListenerAccessGranted(ComponentName listener) {
+            Preconditions.checkNotNull(listener);
+            checkCallerIsSystemOrSameApp(listener.getPackageName());
+            return mListeners.isPackageOrComponentAllowed(listener.flattenToString(),
+                    getCallingUserHandle().getIdentifier());
+        }
+
+        @Override
+        public boolean isNotificationListenerAccessGrantedForUser(ComponentName listener,
+                int userId) {
+            Preconditions.checkNotNull(listener);
+            checkCallerIsSystem();
+            return mListeners.isPackageOrComponentAllowed(listener.flattenToString(),
+                    userId);
+        }
+
+        @Override
+        public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
+            Preconditions.checkNotNull(assistant);
+            checkCallerIsSystemOrSameApp(assistant.getPackageName());
+            return mAssistants.isPackageOrComponentAllowed(assistant.flattenToString(),
+                    getCallingUserHandle().getIdentifier());
+        }
+
+        @Override
+        public void setNotificationListenerAccessGranted(ComponentName listener,
+                boolean granted) throws RemoteException {
+            setNotificationListenerAccessGrantedForUser(
+                    listener, getCallingUserHandle().getIdentifier(), granted);
+        }
+
+        @Override
+        public void setNotificationAssistantAccessGranted(ComponentName assistant,
+                boolean granted) throws RemoteException {
+            setNotificationAssistantAccessGrantedForUser(
+                    assistant, getCallingUserHandle().getIdentifier(), granted);
+        }
+
+        @Override
+        public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
+                boolean granted) throws RemoteException {
+            Preconditions.checkNotNull(listener);
+            checkCallerIsSystemOrShell();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (!mActivityManager.isLowRamDevice()) {
+                    mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(),
+                            userId, false, granted);
+                    mListeners.setPackageOrComponentEnabled(listener.flattenToString(),
+                            userId, true, granted);
+
+                    getContext().sendBroadcastAsUser(new Intent(
+                            NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                                    .setPackage(listener.getPackageName())
+                                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+                            UserHandle.of(userId), null);
+
+                    savePolicyFile();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant,
+                int userId, boolean granted) throws RemoteException {
+            Preconditions.checkNotNull(assistant);
+            checkCallerIsSystemOrShell();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (!mActivityManager.isLowRamDevice()) {
+                    mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
+                            userId, false, granted);
+                    mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
+                            userId, true, granted);
+
+                    getContext().sendBroadcastAsUser(new Intent(
+                            NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                                    .setPackage(assistant.getPackageName())
+                                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+                            UserHandle.of(userId), null);
+
+                    savePolicyFile();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void applyEnqueuedAdjustmentFromAssistant(INotificationListener token,
+                Adjustment adjustment) throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mNotificationLock) {
+                    mAssistants.checkServiceTokenLocked(token);
+                    int N = mEnqueuedNotifications.size();
+                    for (int i = 0; i < N; i++) {
+                        final NotificationRecord n = mEnqueuedNotifications.get(i);
+                        if (Objects.equals(adjustment.getKey(), n.getKey())
+                                && Objects.equals(adjustment.getUser(), n.getUserId())) {
+                            applyAdjustment(n, adjustment);
+                            break;
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void applyAdjustmentFromAssistant(INotificationListener token,
                 Adjustment adjustment) throws RemoteException {
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
-                    mRankerServices.checkServiceTokenLocked(token);
-                    applyAdjustmentLocked(adjustment);
+                synchronized (mNotificationLock) {
+                    mAssistants.checkServiceTokenLocked(token);
+                    NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
+                    applyAdjustment(n, adjustment);
                 }
-                maybeAddAutobundleSummary(adjustment);
                 mRankingHandler.requestSort();
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -2187,136 +2958,214 @@ public class NotificationManagerService extends SystemService {
         }
 
         @Override
-        public void applyAdjustmentsFromRankerService(INotificationListener token,
+        public void applyAdjustmentsFromAssistant(INotificationListener token,
                 List<Adjustment> adjustments) throws RemoteException {
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationList) {
-                    mRankerServices.checkServiceTokenLocked(token);
+                synchronized (mNotificationLock) {
+                    mAssistants.checkServiceTokenLocked(token);
                     for (Adjustment adjustment : adjustments) {
-                        applyAdjustmentLocked(adjustment);
+                        NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
+                        applyAdjustment(n, adjustment);
                     }
                 }
-                for (Adjustment adjustment : adjustments) {
-                    maybeAddAutobundleSummary(adjustment);
-                }
                 mRankingHandler.requestSort();
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
         }
-    };
 
-    private void applyAdjustmentLocked(Adjustment adjustment) {
-        maybeClearAutobundleSummaryLocked(adjustment);
-        NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
-        if (n == null) {
-            return;
+        @Override
+        public void updateNotificationChannelFromPrivilegedListener(INotificationListener token,
+                String pkg, UserHandle user, NotificationChannel channel) throws RemoteException {
+            Preconditions.checkNotNull(channel);
+            Preconditions.checkNotNull(pkg);
+            Preconditions.checkNotNull(user);
+
+            verifyPrivilegedListener(token, user);
+            updateNotificationChannelInt(pkg, getUidForPackageAndUser(pkg, user), channel, true);
         }
-        if (adjustment.getImportance() != IMPORTANCE_NONE) {
-            n.setImportance(adjustment.getImportance(), adjustment.getExplanation());
+
+        @Override
+        public ParceledListSlice<NotificationChannel> getNotificationChannelsFromPrivilegedListener(
+                INotificationListener token, String pkg, UserHandle user) throws RemoteException {
+            Preconditions.checkNotNull(pkg);
+            Preconditions.checkNotNull(user);
+            verifyPrivilegedListener(token, user);
+
+            return mRankingHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
+                    false /* includeDeleted */);
         }
-        if (adjustment.getSignals() != null) {
-            Bundle.setDefusable(adjustment.getSignals(), true);
-            final String autoGroupKey = adjustment.getSignals().getString(
-                    Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
-            if (autoGroupKey == null) {
-                EventLogTags.writeNotificationUnautogrouped(adjustment.getKey());
-            } else {
-                EventLogTags.writeNotificationAutogrouped(adjustment.getKey());
+
+        @Override
+        public ParceledListSlice<NotificationChannelGroup>
+                getNotificationChannelGroupsFromPrivilegedListener(
+                INotificationListener token, String pkg, UserHandle user) throws RemoteException {
+            Preconditions.checkNotNull(pkg);
+            Preconditions.checkNotNull(user);
+            verifyPrivilegedListener(token, user);
+
+            List<NotificationChannelGroup> groups = new ArrayList<>();
+            groups.addAll(mRankingHelper.getNotificationChannelGroups(
+                    pkg, getUidForPackageAndUser(pkg, user)));
+            return new ParceledListSlice<>(groups);
+        }
+
+        private void verifyPrivilegedListener(INotificationListener token, UserHandle user) {
+            ManagedServiceInfo info;
+            synchronized (mNotificationLock) {
+                info = mListeners.checkServiceTokenLocked(token);
+            }
+            if (!hasCompanionDevice(info)) {
+                throw new SecurityException(info + " does not have access");
+            }
+            if (!info.enabledAndUserMatches(user.getIdentifier())) {
+                throw new SecurityException(info + " does not have access");
             }
-            n.sbn.setOverrideGroupKey(autoGroupKey);
         }
-    }
 
-    // Clears the 'fake' auto-bunding summary.
-    private void maybeClearAutobundleSummaryLocked(Adjustment adjustment) {
+        private int getUidForPackageAndUser(String pkg, UserHandle user) throws RemoteException {
+            int uid = 0;
+            long identity = Binder.clearCallingIdentity();
+            try {
+                uid = mPackageManager.getPackageUid(pkg, 0, user.getIdentifier());
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            return uid;
+        }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+                throws RemoteException {
+            new ShellCmd().exec(this, in, out, err, args, callback, resultReceiver);
+        }
+    };
+
+    private void applyAdjustment(NotificationRecord r, Adjustment adjustment) {
+        if (r == null) {
+            return;
+        }
         if (adjustment.getSignals() != null) {
             Bundle.setDefusable(adjustment.getSignals(), true);
-            if (adjustment.getSignals().containsKey(Adjustment.NEEDS_AUTOGROUPING_KEY)
-                && !adjustment.getSignals().getBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false)) {
-                ArrayMap<String, String> summaries =
-                        mAutobundledSummaries.get(adjustment.getUser());
-                if (summaries != null && summaries.containsKey(adjustment.getPackage())) {
-                    // Clear summary.
-                    final NotificationRecord removed = mNotificationsByKey.get(
-                            summaries.remove(adjustment.getPackage()));
-                    if (removed != null) {
-                        mNotificationList.remove(removed);
-                        cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
-                    }
-                }
+            r.addAdjustment(adjustment);
+        }
+    }
+
+    @GuardedBy("mNotificationLock")
+    void addAutogroupKeyLocked(String key) {
+        NotificationRecord r = mNotificationsByKey.get(key);
+        if (r == null) {
+            return;
+        }
+        if (r.sbn.getOverrideGroupKey() == null) {
+            addAutoGroupAdjustment(r, GroupHelper.AUTOGROUP_KEY);
+            EventLogTags.writeNotificationAutogrouped(key);
+            mRankingHandler.requestSort();
+        }
+    }
+
+    @GuardedBy("mNotificationLock")
+    void removeAutogroupKeyLocked(String key) {
+        NotificationRecord r = mNotificationsByKey.get(key);
+        if (r == null) {
+            return;
+        }
+        if (r.sbn.getOverrideGroupKey() != null) {
+            addAutoGroupAdjustment(r, null);
+            EventLogTags.writeNotificationUnautogrouped(key);
+            mRankingHandler.requestSort();
+        }
+    }
+
+    private void addAutoGroupAdjustment(NotificationRecord r, String overrideGroupKey) {
+        Bundle signals = new Bundle();
+        signals.putString(Adjustment.KEY_GROUP_KEY, overrideGroupKey);
+        Adjustment adjustment =
+                new Adjustment(r.sbn.getPackageName(), r.getKey(), signals, "", r.sbn.getUserId());
+        r.addAdjustment(adjustment);
+    }
+
+    // Clears the 'fake' auto-group summary.
+    @GuardedBy("mNotificationLock")
+    private void clearAutogroupSummaryLocked(int userId, String pkg) {
+        ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
+        if (summaries != null && summaries.containsKey(pkg)) {
+            // Clear summary.
+            final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg));
+            if (removed != null) {
+                boolean wasPosted = removeFromNotificationListsLocked(removed);
+                cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED, wasPosted, null);
             }
         }
     }
 
+    @GuardedBy("mNotificationLock")
+    private boolean hasAutoGroupSummaryLocked(StatusBarNotification sbn) {
+        ArrayMap<String, String> summaries = mAutobundledSummaries.get(sbn.getUserId());
+        return summaries != null && summaries.containsKey(sbn.getPackageName());
+    }
+
     // Posts a 'fake' summary for a package that has exceeded the solo-notification limit.
-    private void maybeAddAutobundleSummary(Adjustment adjustment) {
-        if (adjustment.getSignals() != null) {
-            Bundle.setDefusable(adjustment.getSignals(), true);
-            if (adjustment.getSignals().getBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false)) {
-                final String newAutoBundleKey =
-                        adjustment.getSignals().getString(Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
-                int userId = -1;
-                NotificationRecord summaryRecord = null;
-                synchronized (mNotificationList) {
-                    NotificationRecord notificationRecord =
-                            mNotificationsByKey.get(adjustment.getKey());
-                    if (notificationRecord == null) {
-                        // The notification could have been cancelled again already. A successive
-                        // adjustment will post a summary if needed.
-                        return;
-                    }
-                    final StatusBarNotification adjustedSbn = notificationRecord.sbn;
-                    userId = adjustedSbn.getUser().getIdentifier();
-                    ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
-                    if (summaries == null) {
-                        summaries = new ArrayMap<>();
-                    }
-                    mAutobundledSummaries.put(userId, summaries);
-                    if (!summaries.containsKey(adjustment.getPackage())
-                            && newAutoBundleKey != null) {
-                        // Add summary
-                        final ApplicationInfo appInfo =
-                                adjustedSbn.getNotification().extras.getParcelable(
-                                        Notification.EXTRA_BUILDER_APPLICATION_INFO);
-                        final Bundle extras = new Bundle();
-                        extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
-                        final Notification summaryNotification =
-                                new Notification.Builder(getContext()).setSmallIcon(
-                                        adjustedSbn.getNotification().getSmallIcon())
-                                        .setGroupSummary(true)
-                                        .setGroup(newAutoBundleKey)
-                                        .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true)
-                                        .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
-                                        .setColor(adjustedSbn.getNotification().color)
-                                        .setLocalOnly(true)
-                                        .build();
-                        summaryNotification.extras.putAll(extras);
-                        Intent appIntent = getContext().getPackageManager()
-                                .getLaunchIntentForPackage(adjustment.getPackage());
-                        if (appIntent != null) {
-                            summaryNotification.contentIntent = PendingIntent.getActivityAsUser(
-                                    getContext(), 0, appIntent, 0, null,
-                                    UserHandle.of(userId));
-                        }
-                        final StatusBarNotification summarySbn =
-                                new StatusBarNotification(adjustedSbn.getPackageName(),
-                                        adjustedSbn.getOpPkg(),
-                                        Integer.MAX_VALUE, Adjustment.GROUP_KEY_OVERRIDE_KEY,
-                                        adjustedSbn.getUid(), adjustedSbn.getInitialPid(),
-                                        summaryNotification, adjustedSbn.getUser(),
-                                        newAutoBundleKey,
-                                        System.currentTimeMillis());
-                        summaryRecord = new NotificationRecord(getContext(), summarySbn);
-                        summaries.put(adjustment.getPackage(), summarySbn.getKey());
-                    }
-                }
-                if (summaryRecord != null) {
-                    mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord));
-                }
+    private void createAutoGroupSummary(int userId, String pkg, String triggeringKey) {
+        NotificationRecord summaryRecord = null;
+        synchronized (mNotificationLock) {
+            NotificationRecord notificationRecord = mNotificationsByKey.get(triggeringKey);
+            if (notificationRecord == null) {
+                // The notification could have been cancelled again already. A successive
+                // adjustment will post a summary if needed.
+                return;
             }
+            final StatusBarNotification adjustedSbn = notificationRecord.sbn;
+            userId = adjustedSbn.getUser().getIdentifier();
+            ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
+            if (summaries == null) {
+                summaries = new ArrayMap<>();
+            }
+            mAutobundledSummaries.put(userId, summaries);
+            if (!summaries.containsKey(pkg)) {
+                // Add summary
+                final ApplicationInfo appInfo =
+                       adjustedSbn.getNotification().extras.getParcelable(
+                               Notification.EXTRA_BUILDER_APPLICATION_INFO);
+                final Bundle extras = new Bundle();
+                extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
+                final String channelId = notificationRecord.getChannel().getId();
+                final Notification summaryNotification =
+                        new Notification.Builder(getContext(), channelId)
+                                .setSmallIcon(adjustedSbn.getNotification().getSmallIcon())
+                                .setGroupSummary(true)
+                                .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+                                .setGroup(GroupHelper.AUTOGROUP_KEY)
+                                .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true)
+                                .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
+                                .setColor(adjustedSbn.getNotification().color)
+                                .setLocalOnly(true)
+                                .build();
+                summaryNotification.extras.putAll(extras);
+                Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
+                if (appIntent != null) {
+                    summaryNotification.contentIntent = PendingIntent.getActivityAsUser(
+                            getContext(), 0, appIntent, 0, null, UserHandle.of(userId));
+                }
+                final StatusBarNotification summarySbn =
+                        new StatusBarNotification(adjustedSbn.getPackageName(),
+                                adjustedSbn.getOpPkg(),
+                                Integer.MAX_VALUE,
+                                GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
+                                adjustedSbn.getInitialPid(), summaryNotification,
+                                adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
+                                System.currentTimeMillis());
+                summaryRecord = new NotificationRecord(getContext(), summarySbn,
+                        notificationRecord.getChannel());
+                summaries.put(pkg, summarySbn.getKey());
+            }
+        }
+        if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
+                summaryRecord.sbn.getId(), summaryRecord.sbn.getTag(), summaryRecord, true)) {
+            mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord));
         }
     }
 
@@ -2340,12 +3189,58 @@ public class NotificationManagerService extends SystemService {
             dump.put("bans", mRankingHelper.dumpBansJson(filter));
             dump.put("ranking", mRankingHelper.dumpJson(filter));
             dump.put("stats", mUsageStats.dumpJson(filter));
+            dump.put("channels", mRankingHelper.dumpChannelsJson(filter));
         } catch (JSONException e) {
             e.printStackTrace();
         }
         pw.println(dump);
     }
 
+    private void dumpProto(FileDescriptor fd, DumpFilter filter) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        synchronized (mNotificationLock) {
+            long records = proto.start(NotificationServiceDumpProto.RECORDS);
+            int N = mNotificationList.size();
+            if (N > 0) {
+                for (int i = 0; i < N; i++) {
+                    final NotificationRecord nr = mNotificationList.get(i);
+                    if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                    nr.dump(proto, filter.redact);
+                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.POSTED);
+                }
+            }
+            N = mEnqueuedNotifications.size();
+            if (N > 0) {
+                for (int i = 0; i < N; i++) {
+                    final NotificationRecord nr = mEnqueuedNotifications.get(i);
+                    if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                    nr.dump(proto, filter.redact);
+                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.ENQUEUED);
+                }
+            }
+            List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed();
+            N = snoozed.size();
+            if (N > 0) {
+                for (int i = 0; i < N; i++) {
+                    final NotificationRecord nr = snoozed.get(i);
+                    if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                    nr.dump(proto, filter.redact);
+                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.SNOOZED);
+                }
+            }
+            proto.end(records);
+        }
+
+        long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
+        mZenModeHelper.dump(proto);
+        for (ComponentName suppressor : mEffectsSuppressors) {
+            proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+        }
+        proto.end(zenLog);
+
+        proto.flush();
+    }
+
     void dumpImpl(PrintWriter pw, DumpFilter filter) {
         pw.print("Current Notification Manager state");
         if (filter.filtered) {
@@ -2368,7 +3263,7 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
-        synchronized (mNotificationList) {
+        synchronized (mNotificationLock) {
             if (!zenOnly) {
                 N = mNotificationList.size();
                 if (N > 0) {
@@ -2406,30 +3301,31 @@ public class NotificationManagerService extends SystemService {
                 }
                 pw.println("  mArchive=" + mArchive.toString());
                 Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
-                int i=0;
+                int j=0;
                 while (iter.hasNext()) {
                     final StatusBarNotification sbn = iter.next();
                     if (filter != null && !filter.matches(sbn)) continue;
                     pw.println("    " + sbn);
-                    if (++i >= 5) {
+                    if (++j >= 5) {
                         if (iter.hasNext()) pw.println("    ...");
                         break;
                     }
                 }
-            }
 
-            if (!zenOnly) {
-                pw.println("\n  Usage Stats:");
-                mUsageStats.dump(pw, "    ", filter);
-            }
-
-            if (!filter.filtered || zenOnly) {
-                pw.println("\n  Zen Mode:");
-                pw.print("    mInterruptionFilter="); pw.println(mInterruptionFilter);
-                mZenModeHelper.dump(pw, "    ");
+                if (!zenOnly) {
+                    N = mEnqueuedNotifications.size();
+                    if (N > 0) {
+                        pw.println("  Enqueued Notification List:");
+                        for (int i = 0; i < N; i++) {
+                            final NotificationRecord nr = mEnqueuedNotifications.get(i);
+                            if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                            nr.dump(pw, "    ", getContext(), filter.redact);
+                        }
+                        pw.println("  ");
+                    }
 
-                pw.println("\n  Zen Log:");
-                ZenLog.dump(pw, "    ");
+                    mSnoozeHelper.dump(pw, filter);
+                }
             }
 
             if (!zenOnly) {
@@ -2457,12 +3353,18 @@ public class NotificationManagerService extends SystemService {
                     }
                 }
                 pw.println(')');
-                pw.println("\n  mRankerServicePackageName: " + mRankerServicePackageName);
-                pw.println("\n  Notification ranker services:");
-                mRankerServices.dump(pw, filter);
+                pw.println("\n  Notification assistant services:");
+                mAssistants.dump(pw, filter);
+            }
+
+            if (!filter.filtered || zenOnly) {
+                pw.println("\n  Zen Mode:");
+                pw.print("    mInterruptionFilter="); pw.println(mInterruptionFilter);
+                mZenModeHelper.dump(pw, "    ");
+
+                pw.println("\n  Zen Log:");
+                ZenLog.dump(pw, "    ");
             }
-            pw.println("\n  Policy access:");
-            pw.print("    mPolicyAccess: "); pw.println(mPolicyAccess);
 
             pw.println("\n  Condition providers:");
             mConditionProviders.dump(pw, filter);
@@ -2476,6 +3378,11 @@ public class NotificationManagerService extends SystemService {
                     r.dump(pw, "      ", getContext(), filter.redact);
                 }
             }
+
+            if (!zenOnly) {
+                pw.println("\n  Usage Stats:");
+                mUsageStats.dump(pw, "    ", filter);
+            }
         }
     }
 
@@ -2484,57 +3391,92 @@ public class NotificationManagerService extends SystemService {
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
         @Override
+        public NotificationChannel getNotificationChannel(String pkg, int uid, String
+                channelId) {
+            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+        }
+
+        @Override
         public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
-                String tag, int id, Notification notification, int[] idReceived, int userId) {
+                String tag, int id, Notification notification, int userId) {
             enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
-                    idReceived, userId);
+                    userId);
         }
 
         @Override
         public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,
                 int userId) {
             checkCallerIsSystem();
-            synchronized (mNotificationList) {
-                int i = indexOfNotificationLocked(pkg, null, notificationId, userId);
-                if (i < 0) {
-                    Log.d(TAG, "stripForegroundServiceFlag: Could not find notification with "
-                            + "pkg=" + pkg + " / id=" + notificationId + " / userId=" + userId);
-                    return;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (mNotificationLock) {
+                        removeForegroundServiceFlagByListLocked(
+                                mEnqueuedNotifications, pkg, notificationId, userId);
+                        removeForegroundServiceFlagByListLocked(
+                                mNotificationList, pkg, notificationId, userId);
+                    }
                 }
-                NotificationRecord r = mNotificationList.get(i);
-                StatusBarNotification sbn = r.sbn;
-                // NoMan adds flags FLAG_NO_CLEAR and FLAG_ONGOING_EVENT when it sees
-                // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove FLAG_FOREGROUND_SERVICE,
-                // we have to revert to the flags we received initially *and* force remove
-                // FLAG_FOREGROUND_SERVICE.
-                sbn.getNotification().flags =
-                        (r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
-                mRankingHelper.sort(mNotificationList);
-                mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);
+            });
+        }
+
+        @GuardedBy("mNotificationLock")
+        private void removeForegroundServiceFlagByListLocked(
+                ArrayList<NotificationRecord> notificationList, String pkg, int notificationId,
+                int userId) {
+            NotificationRecord r = findNotificationByListLocked(
+                    notificationList, pkg, null, notificationId, userId);
+            if (r == null) {
+                return;
             }
+            StatusBarNotification sbn = r.sbn;
+            // NoMan adds flags FLAG_NO_CLEAR and FLAG_ONGOING_EVENT when it sees
+            // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove
+            // FLAG_FOREGROUND_SERVICE, we have to revert to the flags we received
+            // initially *and* force remove FLAG_FOREGROUND_SERVICE.
+            sbn.getNotification().flags =
+                    (r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
+            mRankingHelper.sort(mNotificationList);
+            mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);
         }
     };
 
     void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
             final int callingPid, final String tag, final int id, final Notification notification,
-            int[] idOut, int incomingUserId) {
+            int incomingUserId) {
         if (DBG) {
             Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                     + " notification=" + notification);
         }
         checkCallerIsSystemOrSameApp(pkg);
-        final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
-        final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
 
         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);
+
         // Fix the notification as best we can.
         try {
-            final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(
+            final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                     pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                     (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
-            Notification.addFieldsFromContext(ai, userId, notification);
+            Notification.addFieldsFromContext(ai, notification);
+
+            int canColorize = mPackageManagerClient.checkPermission(
+                    android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
+            if (canColorize == PERMISSION_GRANTED) {
+                notification.flags |= Notification.FLAG_CAN_COLORIZE;
+            } else {
+                notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
+            }
+
         } catch (NameNotFoundException e) {
             Slog.e(TAG, "Cannot create a context for sending app", e);
             return;
@@ -2542,57 +3484,57 @@ public class NotificationManagerService extends SystemService {
 
         mUsageStats.registerEnqueuedByApp(pkg);
 
-        if (pkg == null || notification == null) {
-            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
-                    + " id=" + id + " notification=" + notification);
+        // setup local book-keeping
+        String channelId = notification.getChannelId();
+        if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
+            channelId = (new Notification.TvExtender(notification)).getChannelId();
+        }
+        final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
+                notificationUid, channelId, false /* includeDeleted */);
+        if (channel == null) {
+            final String noChannelStr = "No Channel found for "
+                    + "pkg=" + pkg
+                    + ", channelId=" + channelId
+                    + ", id=" + id
+                    + ", tag=" + tag
+                    + ", opPkg=" + opPkg
+                    + ", callingUid=" + callingUid
+                    + ", userId=" + userId
+                    + ", incomingUserId=" + incomingUserId
+                    + ", notificationUid=" + notificationUid
+                    + ", notification=" + notification;
+            Log.e(TAG, noChannelStr);
+            doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
+                    "Failed to post notification on channel \"" + channelId + "\"\n" +
+                    "See log for more details");
+            return;
         }
 
-        // The system can post notifications for any package, let us resolve that.
-        final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);
-
         final StatusBarNotification n = new StatusBarNotification(
-                pkg, opPkg, id, tag, notificationUid, callingPid, 0, notification,
-                user);
-
-        // Limit the number of notifications that any given package except the android
-        // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.
-        if (!isSystemNotification && !isNotificationFromListener) {
-            synchronized (mNotificationList) {
-                if(mNotificationsByKey.get(n.getKey()) != null) {
-                    // this is an update, rate limit updates only
-                    final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
-                    if (appEnqueueRate > mMaxPackageEnqueueRate) {
-                        mUsageStats.registerOverRateQuota(pkg);
-                        final long now = SystemClock.elapsedRealtime();
-                        if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
-                            Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
-                                    + ". Shedding events. package=" + pkg);
-                            mLastOverRateLogTime = now;
-                        }
-                        return;
-                    }
-                }
-
-                int count = 0;
-                final int N = mNotificationList.size();
-                for (int i=0; i<N; i++) {
-                    final NotificationRecord r = mNotificationList.get(i);
-                    if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
-                        if (r.sbn.getId() == id && TextUtils.equals(r.sbn.getTag(), tag)) {
-                            break;  // Allow updating existing notification
-                        }
-                        count++;
-                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
-                            mUsageStats.registerOverCountQuota(pkg);
-                            Slog.e(TAG, "Package has already posted " + count
-                                    + " notifications.  Not showing more.  package=" + pkg);
-                            return;
-                        }
-                    }
-                }
+                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
+                user, null, System.currentTimeMillis());
+        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
+
+        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
+                && (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
+                && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) {
+            // Increase the importance of foreground service notifications unless the user had an
+            // opinion otherwise
+            if (TextUtils.isEmpty(channelId)
+                    || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
+                r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
+            } else {
+                channel.setImportance(IMPORTANCE_LOW);
+                mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+                r.updateNotificationChannel(channel);
             }
         }
 
+        if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
+                r.sbn.getOverrideGroupKey() != null)) {
+            return;
+        }
+
         // Whitelist pending intents.
         if (notification.allPendingIntents != null) {
             final int intentCount = notification.allPendingIntents.size();
@@ -2604,26 +3546,30 @@ public class NotificationManagerService extends SystemService {
                 for (int i = 0; i < intentCount; i++) {
                     PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                     if (pendingIntent != null) {
-                        am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);
+                        am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
+                                WHITELIST_TOKEN, duration);
                     }
                 }
             }
         }
 
-        // Sanitize inputs
-        notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
-                Notification.PRIORITY_MAX);
-
-        // setup local book-keeping
-        final NotificationRecord r = new NotificationRecord(getContext(), n);
         mHandler.post(new EnqueueNotificationRunnable(userId, r));
+    }
 
-        idOut[0] = id;
+    private void doChannelWarningToast(CharSequence toastText) {
+        final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0;
+        final boolean warningEnabled = Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, defaultWarningEnabled) != 0;
+        if (warningEnabled) {
+            Toast toast = Toast.makeText(getContext(), mHandler.getLooper(), toastText,
+                    Toast.LENGTH_SHORT);
+            toast.show();
+        }
     }
 
     private int resolveNotificationUid(String opPackageName, int callingUid, int userId) {
         // The system can post notifications on behalf of any package it wants
-        if (isCallerSystem() && opPackageName != null && !"android".equals(opPackageName)) {
+        if (isCallerSystemOrPhone() && opPackageName != null && !"android".equals(opPackageName)) {
             try {
                 return getContext().getPackageManager()
                         .getPackageUidAsUser(opPackageName, userId);
@@ -2634,7 +3580,209 @@ public class NotificationManagerService extends SystemService {
         return callingUid;
     }
 
-    private class EnqueueNotificationRunnable implements Runnable {
+    /**
+     * Checks if a notification can be posted. checks rate limiter, snooze helper, and blocking.
+     *
+     * Has side effects.
+     */
+    private boolean checkDisqualifyingFeatures(int userId, int callingUid, int id, String tag,
+            NotificationRecord r, boolean isAutogroup) {
+        final String pkg = r.sbn.getPackageName();
+        final String dialerPackage =
+                getContext().getSystemService(TelecomManager.class).getSystemDialerPackage();
+        final boolean isSystemNotification =
+                isUidSystemOrPhone(callingUid) || ("android".equals(pkg))
+                || TextUtils.equals(pkg, dialerPackage);
+        final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
+
+        // Limit the number of notifications that any given package except the android
+        // 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)) {
+                    // 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
+                    // notification).
+                    throw new SecurityException("Instant app " + pkg
+                            + " cannot create notifications");
+                }
+
+                // rate limit updates that aren't completed progress notifications
+                if (mNotificationsByKey.get(r.sbn.getKey()) != null
+                        && !r.getNotification().hasCompletedProgress()
+                        && !isAutogroup) {
+
+                    final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
+                    if (appEnqueueRate > mMaxPackageEnqueueRate) {
+                        mUsageStats.registerOverRateQuota(pkg);
+                        final long now = SystemClock.elapsedRealtime();
+                        if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
+                            Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
+                                    + ". Shedding " + r.sbn.getKey() + ". package=" + pkg);
+                            mLastOverRateLogTime = now;
+                        }
+                        return false;
+                    }
+                }
+
+                // limit the number of outstanding notificationrecords an app can have
+                int count = getNotificationCountLocked(pkg, userId, id, tag);
+                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
+                    mUsageStats.registerOverCountQuota(pkg);
+                    Slog.e(TAG, "Package has already posted or enqueued " + count
+                            + " notifications.  Not showing more.  package=" + pkg);
+                    return false;
+                }
+            }
+        }
+
+        // snoozed apps
+        if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
+            MetricsLogger.action(r.getLogMaker()
+                    .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
+                    .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
+            if (DBG) {
+                Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
+            }
+            mSnoozeHelper.update(userId, r);
+            savePolicyFile();
+            return false;
+        }
+
+
+        // blocked apps
+        if (isBlocked(r, mUsageStats)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected int getNotificationCountLocked(String pkg, int userId, int excludedId,
+            String excludedTag) {
+        int count = 0;
+        final int N = mNotificationList.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationRecord existing = mNotificationList.get(i);
+            if (existing.sbn.getPackageName().equals(pkg)
+                    && existing.sbn.getUserId() == userId) {
+                if (existing.sbn.getId() == excludedId
+                        && TextUtils.equals(existing.sbn.getTag(), excludedTag)) {
+                    continue;
+                }
+                count++;
+            }
+        }
+        final int M = mEnqueuedNotifications.size();
+        for (int i = 0; i < M; i++) {
+            final NotificationRecord existing = mEnqueuedNotifications.get(i);
+            if (existing.sbn.getPackageName().equals(pkg)
+                    && existing.sbn.getUserId() == userId) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) {
+        final String pkg = r.sbn.getPackageName();
+        final int callingUid = r.sbn.getUid();
+
+        final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
+        if (isPackageSuspended) {
+            Slog.e(TAG, "Suppressing notification from package due to package "
+                    + "suspended by administrator.");
+            usageStats.registerSuspendedByAdmin(r);
+            return isPackageSuspended;
+        }
+
+        final boolean isBlocked =
+                mRankingHelper.getImportance(pkg, callingUid) == NotificationManager.IMPORTANCE_NONE
+                || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
+        if (isBlocked) {
+            Slog.e(TAG, "Suppressing notification from package by user request.");
+            usageStats.registerBlocked(r);
+        }
+        return isBlocked;
+    }
+
+    protected class SnoozeNotificationRunnable implements Runnable {
+        private final String mKey;
+        private final long mDuration;
+        private final String mSnoozeCriterionId;
+
+        SnoozeNotificationRunnable(String key, long duration, String snoozeCriterionId) {
+            mKey = key;
+            mDuration = duration;
+            mSnoozeCriterionId = snoozeCriterionId;
+        }
+
+        @Override
+        public void run() {
+            synchronized (mNotificationLock) {
+                final NotificationRecord r = findNotificationByKeyLocked(mKey);
+                if (r != null) {
+                    snoozeLocked(r);
+                }
+            }
+        }
+
+        @GuardedBy("mNotificationLock")
+        void snoozeLocked(NotificationRecord r) {
+            if (r.sbn.isGroup()) {
+                final List<NotificationRecord> groupNotifications = findGroupNotificationsLocked(
+                        r.sbn.getPackageName(), r.sbn.getGroupKey(), r.sbn.getUserId());
+                if (r.getNotification().isGroupSummary()) {
+                    // snooze summary and all children
+                    for (int i = 0; i < groupNotifications.size(); i++) {
+                        snoozeNotificationLocked(groupNotifications.get(i));
+                    }
+                } else {
+                    // if there is a valid summary for this group, and we are snoozing the only
+                    // child, also snooze the summary
+                    if (mSummaryByGroupKey.containsKey(r.sbn.getGroupKey())) {
+                        if (groupNotifications.size() != 2) {
+                            snoozeNotificationLocked(r);
+                        } else {
+                            // snooze summary and the one child
+                            for (int i = 0; i < groupNotifications.size(); i++) {
+                                snoozeNotificationLocked(groupNotifications.get(i));
+                            }
+                        }
+                    } else {
+                        snoozeNotificationLocked(r);
+                    }
+                }
+            } else {
+                // just snooze the one notification
+                snoozeNotificationLocked(r);
+            }
+        }
+
+        @GuardedBy("mNotificationLock")
+        void snoozeNotificationLocked(NotificationRecord r) {
+            MetricsLogger.action(r.getLogMaker()
+                    .setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
+                    .setType(MetricsEvent.TYPE_CLOSE)
+                    .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS,
+                            mDuration)
+                    .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
+                            mSnoozeCriterionId == null ? 0 : 1));
+            boolean wasPosted = removeFromNotificationListsLocked(r);
+            cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
+            updateLightsLocked();
+            if (mSnoozeCriterionId != null) {
+                mAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId);
+                mSnoozeHelper.snooze(r);
+            } else {
+                mSnoozeHelper.snooze(r, mDuration);
+            }
+            savePolicyFile();
+        }
+    }
+
+    protected class EnqueueNotificationRunnable implements Runnable {
         private final NotificationRecord r;
         private final int userId;
 
@@ -2645,8 +3793,10 @@ public class NotificationManagerService extends SystemService {
 
         @Override
         public void run() {
+            synchronized (mNotificationLock) {
+                mEnqueuedNotifications.add(r);
+                scheduleTimeoutLocked(r);
 
-            synchronized (mNotificationList) {
                 final StatusBarNotification n = r.sbn;
                 if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
                 NotificationRecord old = mNotificationsByKey.get(n.getKey());
@@ -2661,13 +3811,16 @@ public class NotificationManagerService extends SystemService {
                 final String pkg = n.getPackageName();
                 final int id = n.getId();
                 final String tag = n.getTag();
-                final boolean isSystemNotification = isUidSystem(callingUid) ||
-                        ("android".equals(pkg));
 
                 // Handle grouped notifications and bail out early if we
                 // can to avoid extracting signals.
                 handleGroupedNotificationLocked(r, old, callingUid, callingPid);
 
+                // if this is a group child, unsnooze parent summary
+                if (n.isGroup() && notification.isGroupChild()) {
+                    mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
+                }
+
                 // This conditional is a dirty hack to limit the logging done on
                 //     behalf of the download manager without affecting other apps.
                 if (!pkg.equals("com.android.providers.downloads")
@@ -2683,73 +3836,113 @@ public class NotificationManagerService extends SystemService {
 
                 mRankingHelper.extractSignals(r);
 
-                final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
-
-                // blocked apps
-                if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
-                        || !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {
-                    if (!isSystemNotification) {
-                        if (isPackageSuspended) {
-                            Slog.e(TAG, "Suppressing notification from package due to package "
-                                    + "suspended by administrator.");
-                            mUsageStats.registerSuspendedByAdmin(r);
-                        } else {
-                            Slog.e(TAG, "Suppressing notification from package by user request.");
-                            mUsageStats.registerBlocked(r);
-                        }
-                        return;
-                    }
+                // tell the assistant service about the notification
+                if (mAssistants.isEnabled()) {
+                    mAssistants.onNotificationEnqueued(r);
+                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
+                            DELAY_FOR_ASSISTANT_TIME);
+                } else {
+                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                 }
+            }
+        }
+    }
 
-                // tell the ranker service about the notification
-                if (mRankerServices.isEnabled()) {
-                    mRankerServices.onNotificationEnqueued(r);
-                    // TODO delay the code below here for 100ms or until there is an answer
-                }
+    protected class PostNotificationRunnable implements Runnable {
+        private final String key;
 
+        PostNotificationRunnable(String key) {
+            this.key = key;
+        }
 
-                int index = indexOfNotificationLocked(n.getKey());
-                if (index < 0) {
-                    mNotificationList.add(r);
-                    mUsageStats.registerPostedByApp(r);
-                } else {
-                    old = mNotificationList.get(index);
-                    mNotificationList.set(index, r);
-                    mUsageStats.registerUpdatedByApp(r, old);
-                    // Make sure we don't lose the foreground service state.
-                    notification.flags |=
-                            old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
-                    r.isUpdate = true;
-                }
+        @Override
+        public void run() {
+            synchronized (mNotificationLock) {
+                try {
+                    NotificationRecord r = null;
+                    int N = mEnqueuedNotifications.size();
+                    for (int i = 0; i < N; i++) {
+                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
+                        if (Objects.equals(key, enqueued.getKey())) {
+                            r = enqueued;
+                            break;
+                        }
+                    }
+                    if (r == null) {
+                        Slog.i(TAG, "Cannot find enqueued record for key: " + key);
+                        return;
+                    }
+                    NotificationRecord old = mNotificationsByKey.get(key);
+                    final StatusBarNotification n = r.sbn;
+                    final Notification notification = n.getNotification();
+                    int index = indexOfNotificationLocked(n.getKey());
+                    if (index < 0) {
+                        mNotificationList.add(r);
+                        mUsageStats.registerPostedByApp(r);
+                    } else {
+                        old = mNotificationList.get(index);
+                        mNotificationList.set(index, r);
+                        mUsageStats.registerUpdatedByApp(r, old);
+                        // Make sure we don't lose the foreground service state.
+                        notification.flags |=
+                                old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
+                        r.isUpdate = true;
+                    }
 
-                mNotificationsByKey.put(n.getKey(), r);
+                    mNotificationsByKey.put(n.getKey(), r);
 
-                // Ensure if this is a foreground service that the proper additional
-                // flags are set.
-                if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                    notification.flags |= Notification.FLAG_ONGOING_EVENT
-                            | Notification.FLAG_NO_CLEAR;
-                }
+                    // Ensure if this is a foreground service that the proper additional
+                    // flags are set.
+                    if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                        notification.flags |= Notification.FLAG_ONGOING_EVENT
+                                | Notification.FLAG_NO_CLEAR;
+                    }
 
-                applyZenModeLocked(r);
-                mRankingHelper.sort(mNotificationList);
+                    applyZenModeLocked(r);
+                    mRankingHelper.sort(mNotificationList);
+
+                    if (notification.getSmallIcon() != null) {
+                        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
+                        mListeners.notifyPostedLocked(n, oldSbn);
+                        if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
+                            mHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    mGroupHelper.onNotificationPosted(
+                                            n, hasAutoGroupSummaryLocked(n));
+                                }
+                            });
+                        }
+                    } else {
+                        Slog.e(TAG, "Not posting notification without small icon: " + notification);
+                        if (old != null && !old.isCanceled) {
+                            mListeners.notifyRemovedLocked(n,
+                                    NotificationListenerService.REASON_ERROR);
+                            mHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    mGroupHelper.onNotificationRemoved(n);
+                                }
+                            });
+                        }
+                        // ATTENTION: in a future release we will bail out here
+                        // so that we do not play sounds, show lights, etc. for invalid
+                        // notifications
+                        Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+                                + n.getPackageName());
+                    }
 
-                if (notification.getSmallIcon() != null) {
-                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
-                    mListeners.notifyPostedLocked(n, oldSbn);
-                } else {
-                    Slog.e(TAG, "Not posting notification without small icon: " + notification);
-                    if (old != null && !old.isCanceled) {
-                        mListeners.notifyRemovedLocked(n);
+                    buzzBeepBlinkLocked(r);
+                } finally {
+                    int N = mEnqueuedNotifications.size();
+                    for (int i = 0; i < N; i++) {
+                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
+                        if (Objects.equals(key, enqueued.getKey())) {
+                            mEnqueuedNotifications.remove(i);
+                            break;
+                        }
                     }
-                    // ATTENTION: in a future release we will bail out here
-                    // so that we do not play sounds, show lights, etc. for invalid
-                    // notifications
-                    Slog.e(TAG, "WARNING: In a future release this will crash the app: "
-                            + n.getPackageName());
                 }
-
-                buzzBeepBlinkLocked(r);
             }
         }
     }
@@ -2762,6 +3955,7 @@ public class NotificationManagerService extends SystemService {
      *
      * <p>Updates mSummaryByGroupKey.</p>
      */
+    @GuardedBy("mNotificationLock")
     private void handleGroupedNotificationLocked(NotificationRecord r, NotificationRecord old,
             int callingUid, int callingPid) {
         StatusBarNotification sbn = r.sbn;
@@ -2797,12 +3991,30 @@ public class NotificationManagerService extends SystemService {
         // notification was a summary and the new one isn't, or when the old
         // notification was a summary and its group key changed.
         if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
-            cancelGroupChildrenLocked(old, callingUid, callingPid, null,
-                    REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
+            cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
+                    null);
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mNotificationLock")
+    void scheduleTimeoutLocked(NotificationRecord record) {
+        if (record.getNotification().getTimeoutAfter() > 0) {
+            final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
+                    REQUEST_CODE_TIMEOUT,
+                    new Intent(ACTION_NOTIFICATION_TIMEOUT)
+                            .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+                                    .appendPath(record.getKey()).build())
+                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                            .putExtra(EXTRA_KEY, record.getKey()),
+                    PendingIntent.FLAG_UPDATE_CURRENT);
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi);
         }
     }
 
     @VisibleForTesting
+    @GuardedBy("mNotificationLock")
     void buzzBeepBlinkLocked(NotificationRecord record) {
         boolean buzz = false;
         boolean beep = false;
@@ -2812,145 +4024,67 @@ public class NotificationManagerService extends SystemService {
         final String key = record.getKey();
 
         // Should this notification make noise, vibe, or use the LED?
-        final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT;
-        final boolean canInterrupt = aboveThreshold && !record.isIntercepted();
-        if (DBG || record.isIntercepted())
-            Slog.v(TAG,
-                    "pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt +
-                            " intercept=" + record.isIntercepted()
-            );
-
-        final int currentUser;
-        final long token = Binder.clearCallingIdentity();
-        try {
-            currentUser = ActivityManager.getCurrentUser();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        // If we're not supposed to beep, vibrate, etc. then don't.
-        final String disableEffects = disableNotificationEffects(record);
-        if (disableEffects != null) {
-            ZenLog.traceDisableEffects(record, disableEffects);
-        }
+        final boolean aboveThreshold =
+                record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
 
         // Remember if this notification already owns the notification channels.
         boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
         boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
-
         // These are set inside the conditional if the notification is allowed to make noise.
         boolean hasValidVibrate = false;
         boolean hasValidSound = false;
-        if (disableEffects == null
-                && (record.getUserId() == UserHandle.USER_ALL ||
-                    record.getUserId() == currentUser ||
-                    mUserProfiles.isCurrentProfile(record.getUserId()))
-                && canInterrupt
-                && mSystemReady
-                && mAudioManager != null) {
-            if (DBG) Slog.v(TAG, "Interrupting!");
-
-            // should we use the default notification sound? (indicated either by
-            // DEFAULT_SOUND or because notification.sound is pointing at
-            // Settings.System.NOTIFICATION_SOUND)
-            final boolean useDefaultSound =
-                   (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
-                           Settings.System.DEFAULT_NOTIFICATION_URI
-                                   .equals(notification.sound);
-
-            Uri soundUri = null;
-            if (useDefaultSound) {
-                soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
-
-                // check to see if the default notification sound is silent
-                hasValidSound = mSystemNotificationSound != null;
-            } else if (notification.sound != null) {
-                soundUri = notification.sound;
-                hasValidSound = (soundUri != null);
-            }
-
-            // Does the notification want to specify its own vibration?
-            final boolean hasCustomVibrate = notification.vibrate != null;
-
-            // new in 4.2: if there was supposed to be a sound and we're in vibrate
-            // mode, and no other vibration is specified, we fall back to vibration
-            final boolean convertSoundToVibration =
-                    !hasCustomVibrate
-                            && hasValidSound
-                            && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE);
-
-            // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
-            final boolean useDefaultVibrate =
-                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
-
-            hasValidVibrate = useDefaultVibrate || convertSoundToVibration ||
-                    hasCustomVibrate;
-
-            // We can alert, and we're allowed to alert, but if the developer asked us to only do
-            // it once, and we already have, then don't.
-            if (!(record.isUpdate
-                    && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) {
-
-                sendAccessibilityEvent(notification, record.sbn.getPackageName());
-
-                if (hasValidSound) {
-                    boolean looping =
-                            (notification.flags & Notification.FLAG_INSISTENT) != 0;
-                    AudioAttributes audioAttributes = audioAttributesForNotification(notification);
-                    mSoundNotificationKey = key;
-                    // do not play notifications if stream volume is 0 (typically because
-                    // ringer mode is silent) or if there is a user of exclusive audio focus
-                    if ((mAudioManager.getStreamVolume(
-                            AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)
-                            && !mAudioManager.isAudioFocusExclusive()) {
-                        final long identity = Binder.clearCallingIdentity();
-                        try {
-                            final IRingtonePlayer player =
-                                    mAudioManager.getRingtonePlayer();
-                            if (player != null) {
-                                if (DBG) Slog.v(TAG, "Playing sound " + soundUri
-                                        + " with attributes " + audioAttributes);
-                                player.playAsync(soundUri, record.sbn.getUser(), looping,
-                                        audioAttributes);
-                                beep = true;
-                            }
-                        } catch (RemoteException e) {
-                        } finally {
-                            Binder.restoreCallingIdentity(identity);
+        boolean sentAccessibilityEvent = false;
+        // If the notification will appear in the status bar, it should send an accessibility
+        // event
+        if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
+            sendAccessibilityEvent(notification, record.sbn.getPackageName());
+            sentAccessibilityEvent = true;
+        }
+
+        if (aboveThreshold && isNotificationForCurrentUser(record)) {
+
+            if (mSystemReady && mAudioManager != null) {
+                Uri soundUri = record.getSound();
+                hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
+                long[] vibration = record.getVibration();
+                // Demote sound to vibration if vibration missing & phone in vibration mode.
+                if (vibration == null
+                        && hasValidSound
+                        && (mAudioManager.getRingerModeInternal()
+                        == AudioManager.RINGER_MODE_VIBRATE)
+                        && mAudioManager.getStreamVolume(
+                        AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
+                    vibration = mFallbackVibrationPattern;
+                }
+                hasValidVibrate = vibration != null;
+
+                boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
+                if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
+                    if (!sentAccessibilityEvent) {
+                        sendAccessibilityEvent(notification, record.sbn.getPackageName());
+                        sentAccessibilityEvent = true;
+                    }
+                    if (DBG) Slog.v(TAG, "Interrupting!");
+                    if (hasValidSound) {
+                        mSoundNotificationKey = key;
+                        if (mInCall) {
+                            playInCallNotification();
+                            beep = true;
+                        } else {
+                            beep = playSound(record, soundUri);
                         }
                     }
-                }
 
-                if (hasValidVibrate && !(mAudioManager.getRingerModeInternal()
-                        == AudioManager.RINGER_MODE_SILENT)) {
-                    mVibrateNotificationKey = key;
+                    final boolean ringerModeSilent =
+                            mAudioManager.getRingerModeInternal()
+                                    == AudioManager.RINGER_MODE_SILENT;
+                    if (!mInCall && hasValidVibrate && !ringerModeSilent) {
+                        mVibrateNotificationKey = key;
 
-                    if (useDefaultVibrate || convertSoundToVibration) {
-                        // Escalate privileges so we can use the vibrator even if the
-                        // notifying app does not have the VIBRATE permission.
-                        long identity = Binder.clearCallingIdentity();
-                        try {
-                            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
-                                    useDefaultVibrate ? mDefaultVibrationPattern
-                                            : mFallbackVibrationPattern,
-                                    ((notification.flags & Notification.FLAG_INSISTENT) != 0)
-                                            ? 0: -1, audioAttributesForNotification(notification));
-                            buzz = true;
-                        } finally {
-                            Binder.restoreCallingIdentity(identity);
-                        }
-                    } else if (notification.vibrate.length > 1) {
-                        // If you want your own vibration pattern, you need the VIBRATE
-                        // permission
-                        mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
-                                notification.vibrate,
-                                ((notification.flags & Notification.FLAG_INSISTENT) != 0)
-                                        ? 0: -1, audioAttributesForNotification(notification));
-                        buzz = true;
+                        buzz = playVibration(record, vibration, hasValidSound);
                     }
                 }
             }
-
         }
         // If a notification is updated to remove the actively playing sound or vibrate,
         // cancel that feedback now
@@ -2964,7 +4098,7 @@ public class NotificationManagerService extends SystemService {
         // light
         // release the light
         boolean wasShowLights = mLights.remove(key);
-        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
+        if (record.getLight() != null && aboveThreshold
                 && ((record.getSuppressedVisualEffects()
                 & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
             mLights.add(key);
@@ -2977,35 +4111,149 @@ public class NotificationManagerService extends SystemService {
             updateLightsLocked();
         }
         if (buzz || beep || blink) {
-            if (((record.getSuppressedVisualEffects()
-                    & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {
-                if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
+            MetricsLogger.action(record.getLogMaker()
+                    .setCategory(MetricsEvent.NOTIFICATION_ALERT)
+                    .setType(MetricsEvent.TYPE_OPEN)
+                    .setSubtype((buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)));
+            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
+        }
+    }
+
+    @GuardedBy("mNotificationLock")
+    boolean shouldMuteNotificationLocked(final NotificationRecord record) {
+        // Suppressed because it's a silent update
+        final Notification notification = record.getNotification();
+        if(record.isUpdate
+                && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) {
+            return true;
+        }
+
+        // muted by listener
+        final String disableEffects = disableNotificationEffects(record);
+        if (disableEffects != null) {
+            ZenLog.traceDisableEffects(record, disableEffects);
+            return true;
+        }
+
+        // suppressed due to DND
+        if (record.isIntercepted()) {
+            return true;
+        }
+
+        // Suppressed because another notification in its group handles alerting
+        if (record.sbn.isGroup()) {
+            return notification.suppressAlertingDueToGrouping();
+        }
+
+        // Suppressed for being too recently noisy
+        final String pkg = record.sbn.getPackageName();
+        if (mUsageStats.isAlertRateLimited(pkg)) {
+            Slog.e(TAG, "Muting recently noisy " + record.getKey());
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean playSound(final NotificationRecord record, Uri soundUri) {
+        boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+        // do not play notifications if there is a user of exclusive audio focus
+        // or the device is in vibrate mode
+        if (!mAudioManager.isAudioFocusExclusive() && (mAudioManager.getRingerModeInternal()
+                != AudioManager.RINGER_MODE_VIBRATE || mAudioManager.getStreamVolume(
+                AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+                if (player != null) {
+                    if (DBG) Slog.v(TAG, "Playing sound " + soundUri
+                            + " with attributes " + record.getAudioAttributes());
+                    player.playAsync(soundUri, record.sbn.getUser(), looping,
+                            record.getAudioAttributes());
+                    return true;
+                }
+            } catch (RemoteException e) {
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+        return false;
+    }
+
+    private boolean playVibration(final NotificationRecord record, long[] vibration,
+            boolean delayVibForSound) {
+        // Escalate privileges so we can use the vibrator even if the
+        // notifying app does not have the VIBRATE permission.
+        long identity = Binder.clearCallingIdentity();
+        try {
+            final VibrationEffect effect;
+            try {
+                final boolean insistent =
+                        (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+                effect = VibrationEffect.createWaveform(
+                        vibration, insistent ? 0 : -1 /*repeatIndex*/);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Error creating vibration waveform with pattern: " +
+                        Arrays.toString(vibration));
+                return false;
+            }
+            if (delayVibForSound) {
+                new Thread(() -> {
+                    // delay the vibration by the same amount as the notification sound
+                    final int waitMs = mAudioManager.getFocusRampTimeMs(
+                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                            record.getAudioAttributes());
+                    if (DBG) Slog.v(TAG, "Delaying vibration by " + waitMs + "ms");
+                    try {
+                        Thread.sleep(waitMs);
+                    } catch (InterruptedException e) { }
+                    mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                            effect, record.getAudioAttributes());
+                }).start();
             } else {
-                EventLogTags.writeNotificationAlert(key,
-                        buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
-                mHandler.post(mBuzzBeepBlinked);
+                mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                        effect, record.getAudioAttributes());
             }
+            return true;
+        } finally{
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
-    private static AudioAttributes audioAttributesForNotification(Notification n) {
-        if (n.audioAttributes != null
-                && !Notification.AUDIO_ATTRIBUTES_DEFAULT.equals(n.audioAttributes)) {
-            // the audio attributes are set and different from the default, use them
-            return n.audioAttributes;
-        } else if (n.audioStreamType >= 0 && n.audioStreamType < AudioSystem.getNumStreamTypes()) {
-            // the stream type is valid, use it
-            return new AudioAttributes.Builder()
-                    .setInternalLegacyStreamType(n.audioStreamType)
-                    .build();
-        } else if (n.audioStreamType == AudioSystem.STREAM_DEFAULT) {
-            return Notification.AUDIO_ATTRIBUTES_DEFAULT;
-        } else {
-            Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType));
-            return Notification.AUDIO_ATTRIBUTES_DEFAULT;
+    private boolean isNotificationForCurrentUser(NotificationRecord record) {
+        final int currentUser;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            currentUser = ActivityManager.getCurrentUser();
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
+        return (record.getUserId() == UserHandle.USER_ALL ||
+                record.getUserId() == currentUser ||
+                mUserProfiles.isCurrentProfile(record.getUserId()));
     }
 
+    protected void playInCallNotification() {
+        new Thread() {
+            @Override
+            public void run() {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+                    if (player != null) {
+                        player.play(new Binder(), mInCallNotificationUri,
+                                mInCallNotificationAudioAttributes,
+                                mInCallNotificationVolume, false);
+                    }
+                } catch (RemoteException e) {
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }.start();
+    }
+
+    @GuardedBy("mToastQueue")
     void showNextToastLocked() {
         ToastRecord record = mToastQueue.get(0);
         while (record != null) {
@@ -3032,6 +4280,7 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
+    @GuardedBy("mToastQueue")
     void cancelToastLocked(int index) {
         ToastRecord record = mToastQueue.get(index);
         try {
@@ -3044,7 +4293,7 @@ public class NotificationManagerService extends SystemService {
         }
 
         ToastRecord lastToast = mToastQueue.remove(index);
-        mWindowManagerInternal.removeWindowToken(lastToast.token, true);
+        mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);
 
         keepProcessAliveIfNeededLocked(record.pid);
         if (mToastQueue.size() > 0) {
@@ -3055,6 +4304,7 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
+    @GuardedBy("mToastQueue")
     private void scheduleTimeoutLocked(ToastRecord r)
     {
         mHandler.removeCallbacksAndMessages(r);
@@ -3074,7 +4324,7 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
-    // lock on mToastQueue
+    @GuardedBy("mToastQueue")
     int indexOfToastLocked(String pkg, ITransientNotification callback)
     {
         IBinder cbak = callback.asBinder();
@@ -3082,14 +4332,28 @@ public class NotificationManagerService extends SystemService {
         int len = list.size();
         for (int i=0; i<len; i++) {
             ToastRecord r = list.get(i);
-            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
+            if (r.pkg.equals(pkg) && r.callback.asBinder().equals(cbak)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @GuardedBy("mToastQueue")
+    int indexOfToastPackageLocked(String pkg)
+    {
+        ArrayList<ToastRecord> list = mToastQueue;
+        int len = list.size();
+        for (int i=0; i<len; i++) {
+            ToastRecord r = list.get(i);
+            if (r.pkg.equals(pkg)) {
                 return i;
             }
         }
         return -1;
     }
 
-    // lock on mToastQueue
+    @GuardedBy("mToastQueue")
     void keepProcessAliveIfNeededLocked(int pid)
     {
         int toastCount = 0; // toasts from this pid
@@ -3102,7 +4366,7 @@ public class NotificationManagerService extends SystemService {
             }
         }
         try {
-            mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
+            mAm.setProcessImportant(mForegroundToken, pid, toastCount > 0, "toast");
         } catch (RemoteException e) {
             // Shouldn't happen.
         }
@@ -3113,44 +4377,55 @@ public class NotificationManagerService extends SystemService {
         RankingReconsideration recon = (RankingReconsideration) message.obj;
         recon.run();
         boolean changed;
-        synchronized (mNotificationList) {
+        synchronized (mNotificationLock) {
             final NotificationRecord record = mNotificationsByKey.get(recon.getKey());
             if (record == null) {
                 return;
             }
             int indexBefore = findNotificationRecordIndexLocked(record);
             boolean interceptBefore = record.isIntercepted();
+            float contactAffinityBefore = record.getContactAffinity();
             int visibilityBefore = record.getPackageVisibilityOverride();
             recon.applyChangesLocked(record);
             applyZenModeLocked(record);
             mRankingHelper.sort(mNotificationList);
             int indexAfter = findNotificationRecordIndexLocked(record);
             boolean interceptAfter = record.isIntercepted();
+            float contactAffinityAfter = record.getContactAffinity();
             int visibilityAfter = record.getPackageVisibilityOverride();
             changed = indexBefore != indexAfter || interceptBefore != interceptAfter
                     || visibilityBefore != visibilityAfter;
-            if (interceptBefore && !interceptAfter) {
+            if (interceptBefore && !interceptAfter
+                    && Float.compare(contactAffinityBefore, contactAffinityAfter) != 0) {
                 buzzBeepBlinkLocked(record);
             }
         }
         if (changed) {
-            scheduleSendRankingUpdate();
+            mHandler.scheduleSendRankingUpdate();
         }
     }
 
-    private void handleRankingSort() {
-        synchronized (mNotificationList) {
+    void handleRankingSort() {
+        if (mRankingHelper == null) return;
+        synchronized (mNotificationLock) {
             final int N = mNotificationList.size();
-            ArrayList<String> orderBefore = new ArrayList<String>(N);
-            ArrayList<String> groupOverrideBefore = new ArrayList<>(N);
+            // Any field that can change via one of the extractors needs to be added here.
+            ArrayList<String> orderBefore = new ArrayList<>(N);
             int[] visibilities = new int[N];
-            int[] importances = new int[N];
+            boolean[] showBadges = new boolean[N];
+            ArrayList<NotificationChannel> channelBefore = new ArrayList<>(N);
+            ArrayList<String> groupKeyBefore = new ArrayList<>(N);
+            ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
+            ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
             for (int i = 0; i < N; i++) {
                 final NotificationRecord r = mNotificationList.get(i);
                 orderBefore.add(r.getKey());
-                groupOverrideBefore.add(r.sbn.getGroupKey());
                 visibilities[i] = r.getPackageVisibilityOverride();
-                importances[i] = r.getImportance();
+                showBadges[i] = r.canShowBadge();
+                channelBefore.add(r.getChannel());
+                groupKeyBefore.add(r.getGroupKey());
+                overridePeopleBefore.add(r.getPeopleOverride());
+                snoozeCriteriaBefore.add(r.getSnoozeCriteria());
                 mRankingHelper.extractSignals(r);
             }
             mRankingHelper.sort(mNotificationList);
@@ -3158,15 +4433,19 @@ public class NotificationManagerService extends SystemService {
                 final NotificationRecord r = mNotificationList.get(i);
                 if (!orderBefore.get(i).equals(r.getKey())
                         || visibilities[i] != r.getPackageVisibilityOverride()
-                        || importances[i] != r.getImportance()
-                        || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())) {
-                    scheduleSendRankingUpdate();
+                        || showBadges[i] != r.canShowBadge()
+                        || !Objects.equals(channelBefore.get(i), r.getChannel())
+                        || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
+                        || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
+                        || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())) {
+                    mHandler.scheduleSendRankingUpdate();
                     return;
                 }
             }
         }
     }
 
+    @GuardedBy("mNotificationLock")
     private void recordCallerLocked(NotificationRecord record) {
         if (mZenModeHelper.isCall(record)) {
             mZenModeHelper.recordCaller(record);
@@ -3174,6 +4453,7 @@ public class NotificationManagerService extends SystemService {
     }
 
     // let zen mode evaluate this record
+    @GuardedBy("mNotificationLock")
     private void applyZenModeLocked(NotificationRecord record) {
         record.setIntercepted(mZenModeHelper.shouldIntercept(record));
         if (record.isIntercepted()) {
@@ -3182,23 +4462,18 @@ public class NotificationManagerService extends SystemService {
                     | (mZenModeHelper.shouldSuppressWhenScreenOn()
                     ? SUPPRESSED_EFFECT_SCREEN_ON : 0);
             record.setSuppressedVisualEffects(suppressed);
+        } else {
+            record.setSuppressedVisualEffects(0);
         }
     }
 
-    // lock on mNotificationList
+    @GuardedBy("mNotificationLock")
     private int findNotificationRecordIndexLocked(NotificationRecord target) {
         return mRankingHelper.indexOf(mNotificationList, target);
     }
 
-    private void scheduleSendRankingUpdate() {
-        if (!mHandler.hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
-            Message m = Message.obtain(mHandler, MESSAGE_SEND_RANKING_UPDATE);
-            mHandler.sendMessage(m);
-        }
-    }
-
     private void handleSendRankingUpdate() {
-        synchronized (mNotificationList) {
+        synchronized (mNotificationLock) {
             mListeners.notifyRankingUpdateLocked();
         }
     }
@@ -3217,19 +4492,23 @@ public class NotificationManagerService extends SystemService {
     }
 
     private void handleListenerHintsChanged(int hints) {
-        synchronized (mNotificationList) {
+        synchronized (mNotificationLock) {
             mListeners.notifyListenerHintsChangedLocked(hints);
         }
     }
 
     private void handleListenerInterruptionFilterChanged(int interruptionFilter) {
-        synchronized (mNotificationList) {
+        synchronized (mNotificationLock) {
             mListeners.notifyInterruptionFilterChanged(interruptionFilter);
         }
     }
 
-    private final class WorkerHandler extends Handler
+    protected class WorkerHandler extends Handler
     {
+        public WorkerHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg)
         {
@@ -3253,6 +4532,13 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
+        protected void scheduleSendRankingUpdate() {
+            if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
+                Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
+                sendMessage(m);
+            }
+        }
+
     }
 
     private final class RankingHandlerWorker extends Handler implements RankingHandler
@@ -3275,7 +4561,9 @@ public class NotificationManagerService extends SystemService {
 
         public void requestSort() {
             removeMessages(MESSAGE_RANKING_SORT);
-            sendEmptyMessage(MESSAGE_RANKING_SORT);
+            Message msg = Message.obtain();
+            msg.what = MESSAGE_RANKING_SORT;
+            sendMessage(msg);
         }
 
         public void requestReconsideration(RankingReconsideration recon) {
@@ -3293,8 +4581,7 @@ public class NotificationManagerService extends SystemService {
     }
 
     void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
-        AccessibilityManager manager = AccessibilityManager.getInstance(getContext());
-        if (!manager.isEnabled()) {
+        if (!mAccessibilityManager.isEnabled()) {
             return;
         }
 
@@ -3308,10 +4595,36 @@ public class NotificationManagerService extends SystemService {
             event.getText().add(tickerText);
         }
 
-        manager.sendAccessibilityEvent(event);
+        mAccessibilityManager.sendAccessibilityEvent(event);
+    }
+
+    /**
+     * Removes all NotificationsRecords with the same key as the given notification record
+     * from both lists. Do not call this method while iterating over either list.
+     */
+    @GuardedBy("mNotificationLock")
+    private boolean removeFromNotificationListsLocked(NotificationRecord r) {
+        // Remove from both lists, either list could have a separate Record for what is
+        // effectively the same notification.
+        boolean wasPosted = false;
+        NotificationRecord recordInList = null;
+        if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey()))
+                != null) {
+            mNotificationList.remove(recordInList);
+            mNotificationsByKey.remove(recordInList.sbn.getKey());
+            wasPosted = true;
+        }
+        while ((recordInList = findNotificationByListLocked(mEnqueuedNotifications, r.getKey()))
+                != null) {
+            mEnqueuedNotifications.remove(recordInList);
+        }
+        return wasPosted;
     }
 
-    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
+    @GuardedBy("mNotificationLock")
+    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason,
+            boolean wasPosted, String listenerName) {
+        final String canceledKey = r.getKey();
 
         // Record caller.
         recordCallerLocked(r);
@@ -3329,49 +4642,58 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
-        // status bar
-        if (r.getNotification().getSmallIcon() != null) {
-            r.isCanceled = true;
-            mListeners.notifyRemovedLocked(r.sbn);
-        }
-
-        final String canceledKey = r.getKey();
-
-        // sound
-        if (canceledKey.equals(mSoundNotificationKey)) {
-            mSoundNotificationKey = null;
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
-                if (player != null) {
-                    player.stopAsync();
+        // Only cancel these if this notification actually got to be posted.
+        if (wasPosted) {
+            // status bar
+            if (r.getNotification().getSmallIcon() != null) {
+                if (reason != REASON_SNOOZED) {
+                    r.isCanceled = true;
                 }
-            } catch (RemoteException e) {
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                mListeners.notifyRemovedLocked(r.sbn, reason);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mGroupHelper.onNotificationRemoved(r.sbn);
+                    }
+                });
             }
-        }
 
-        // vibrate
-        if (canceledKey.equals(mVibrateNotificationKey)) {
-            mVibrateNotificationKey = null;
-            long identity = Binder.clearCallingIdentity();
-            try {
-                mVibrator.cancel();
+            // sound
+            if (canceledKey.equals(mSoundNotificationKey)) {
+                mSoundNotificationKey = null;
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+                    if (player != null) {
+                        player.stopAsync();
+                    }
+                } catch (RemoteException e) {
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
-            finally {
-                Binder.restoreCallingIdentity(identity);
+
+            // vibrate
+            if (canceledKey.equals(mVibrateNotificationKey)) {
+                mVibrateNotificationKey = null;
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    mVibrator.cancel();
+                }
+                finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
-        }
 
-        // light
-        mLights.remove(canceledKey);
+            // light
+            mLights.remove(canceledKey);
+        }
 
         // Record usage stats
         // TODO: add unbundling stats?
         switch (reason) {
-            case REASON_DELEGATE_CANCEL:
-            case REASON_DELEGATE_CANCEL_ALL:
+            case REASON_CANCEL:
+            case REASON_CANCEL_ALL:
             case REASON_LISTENER_CANCEL:
             case REASON_LISTENER_CANCEL_ALL:
                 mUsageStats.registerDismissedByUser(r);
@@ -3382,10 +4704,9 @@ public class NotificationManagerService extends SystemService {
                 break;
         }
 
-        mNotificationsByKey.remove(r.sbn.getKey());
         String groupKey = r.getGroupKey();
         NotificationRecord groupSummary = mSummaryByGroupKey.get(groupKey);
-        if (groupSummary != null && groupSummary.getKey().equals(r.getKey())) {
+        if (groupSummary != null && groupSummary.getKey().equals(canceledKey)) {
             mSummaryByGroupKey.remove(groupKey);
         }
         final ArrayMap<String, String> summaries = mAutobundledSummaries.get(r.sbn.getUserId());
@@ -3397,8 +4718,12 @@ public class NotificationManagerService extends SystemService {
         mArchive.record(r.sbn);
 
         final long now = System.currentTimeMillis();
+        MetricsLogger.action(r.getLogMaker(now)
+                .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+                .setType(MetricsEvent.TYPE_DISMISS)
+                .setSubtype(reason));
         EventLogTags.writeNotificationCanceled(canceledKey, reason,
-                r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
+                r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), listenerName);
     }
 
     /**
@@ -3420,14 +4745,15 @@ public class NotificationManagerService extends SystemService {
                 if (DBG) EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag,
                         userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName);
 
-                synchronized (mNotificationList) {
-                    int index = indexOfNotificationLocked(pkg, tag, id, userId);
-                    if (index >= 0) {
-                        NotificationRecord r = mNotificationList.get(index);
+                synchronized (mNotificationLock) {
+                    // Look for the notification, searching both the posted and enqueued lists.
+                    NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
+                    if (r != null) {
+                        // The notification was found, check if it should be removed.
 
                         // Ideally we'd do this in the caller of this method. However, that would
                         // require the caller to also find the notification.
-                        if (reason == REASON_DELEGATE_CLICK) {
+                        if (reason == REASON_CLICK) {
                             mUsageStats.registerClickedByUser(r);
                         }
 
@@ -3438,12 +4764,20 @@ public class NotificationManagerService extends SystemService {
                             return;
                         }
 
-                        mNotificationList.remove(index);
-
-                        cancelNotificationLocked(r, sendDelete, reason);
+                        // Cancel the notification.
+                        boolean wasPosted = removeFromNotificationListsLocked(r);
+                        cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
                         cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
-                                REASON_GROUP_SUMMARY_CANCELED, sendDelete);
+                                sendDelete, null);
                         updateLightsLocked();
+                    } else {
+                        // No notification was found, assume that it is snoozed and cancel it.
+                        if (reason != REASON_SNOOZED) {
+                            final boolean wasSnoozed = mSnoozeHelper.cancel(userId, pkg, tag, id);
+                            if (wasSnoozed) {
+                                savePolicyFile();
+                            }
+                        }
                     }
                 }
             }
@@ -3478,130 +4812,202 @@ public class NotificationManagerService extends SystemService {
      * Cancels all notifications from a given package that have all of the
      * {@code mustHaveFlags}.
      */
-    boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
-            int mustNotHaveFlags, boolean doit, int userId, int reason,
+    void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
+            int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
             ManagedServiceInfo listener) {
-        String listenerName = listener == null ? null : listener.component.toShortString();
-        EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
-                pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
-                listenerName);
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                String listenerName = listener == null ? null : listener.component.toShortString();
+                EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+                        pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
+                        listenerName);
 
-        synchronized (mNotificationList) {
-            final int N = mNotificationList.size();
-            ArrayList<NotificationRecord> canceledNotifications = null;
-            for (int i = N-1; i >= 0; --i) {
-                NotificationRecord r = mNotificationList.get(i);
-                if (!notificationMatchesUserId(r, userId)) {
-                    continue;
-                }
-                // Don't remove notifications to all, if there's no package name specified
-                if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
-                    continue;
-                }
-                if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) {
-                    continue;
-                }
-                if ((r.getFlags() & mustNotHaveFlags) != 0) {
-                    continue;
-                }
-                if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
-                    continue;
-                }
-                if (canceledNotifications == null) {
-                    canceledNotifications = new ArrayList<>();
-                }
-                canceledNotifications.add(r);
+                // Why does this parameter exist? Do we actually want to execute the above if doit
+                // is false?
                 if (!doit) {
-                    return true;
+                    return;
                 }
-                mNotificationList.remove(i);
-                cancelNotificationLocked(r, false, reason);
-            }
-            if (doit && canceledNotifications != null) {
-                final int M = canceledNotifications.size();
-                for (int i = 0; i < M; i++) {
-                    cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
-                            listenerName, REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
+
+                synchronized (mNotificationLock) {
+                    FlagChecker flagChecker = (int flags) -> {
+                        if ((flags & mustHaveFlags) != mustHaveFlags) {
+                            return false;
+                        }
+                        if ((flags & mustNotHaveFlags) != 0) {
+                            return false;
+                        }
+                        return true;
+                    };
+                    cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+                            pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+                            false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+                            listenerName, true /* wasPosted */);
+                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
+                            callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
+                            flagChecker, false /*includeCurrentProfiles*/, userId,
+                            false /*sendDelete*/, reason, listenerName, false /* wasPosted */);
+                    mSnoozeHelper.cancel(userId, pkg);
                 }
             }
-            if (canceledNotifications != null) {
-                updateLightsLocked();
-            }
-            return canceledNotifications != null;
-        }
+        });
     }
 
-    void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
-            ManagedServiceInfo listener, boolean includeCurrentProfiles) {
-        String listenerName = listener == null ? null : listener.component.toShortString();
-        EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
-                null, userId, 0, 0, reason, listenerName);
+    private interface FlagChecker {
+        // Returns false if these flags do not pass the defined flag test.
+        public boolean apply(int flags);
+    }
 
+    @GuardedBy("mNotificationLock")
+    private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
+            int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
+            String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
+            boolean sendDelete, int reason, String listenerName, boolean wasPosted) {
         ArrayList<NotificationRecord> canceledNotifications = null;
-        final int N = mNotificationList.size();
-        for (int i=N-1; i>=0; i--) {
-            NotificationRecord r = mNotificationList.get(i);
+        for (int i = notificationList.size() - 1; i >= 0; --i) {
+            NotificationRecord r = notificationList.get(i);
             if (includeCurrentProfiles) {
                 if (!notificationMatchesCurrentProfiles(r, userId)) {
                     continue;
                 }
-            } else {
-                if (!notificationMatchesUserId(r, userId)) {
-                    continue;
-                }
+            } else if (!notificationMatchesUserId(r, userId)) {
+                continue;
             }
-
-            if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
-                            | Notification.FLAG_NO_CLEAR)) == 0) {
-                mNotificationList.remove(i);
-                cancelNotificationLocked(r, true, reason);
-                // Make a note so we can cancel children later.
-                if (canceledNotifications == null) {
-                    canceledNotifications = new ArrayList<>();
-                }
-                canceledNotifications.add(r);
+            // Don't remove notifications to all, if there's no package name specified
+            if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == UserHandle.USER_ALL) {
+                continue;
+            }
+            if (!flagChecker.apply(r.getFlags())) {
+                continue;
+            }
+            if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
+                continue;
             }
+            if (channelId != null && !channelId.equals(r.getChannel().getId())) {
+                continue;
+            }
+            if (canceledNotifications == null) {
+                canceledNotifications = new ArrayList<>();
+            }
+            notificationList.remove(i);
+            mNotificationsByKey.remove(r.getKey());
+            canceledNotifications.add(r);
+            cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
         }
-        int M = canceledNotifications != null ? canceledNotifications.size() : 0;
-        for (int i = 0; i < M; i++) {
-            cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
-                    listenerName, REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
+        if (canceledNotifications != null) {
+            final int M = canceledNotifications.size();
+            for (int i = 0; i < M; i++) {
+                cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
+                        listenerName, false /* sendDelete */, flagChecker);
+            }
+            updateLightsLocked();
         }
-        updateLightsLocked();
+    }
+
+    void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
+            ManagedServiceInfo listener) {
+        String listenerName = listener == null ? null : listener.component.toShortString();
+        if (duration <= 0 && snoozeCriterionId == null || key == null) {
+            return;
+        }
+
+        if (DBG) {
+            Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
+                    snoozeCriterionId, listenerName));
+        }
+        // Needs to post so that it can cancel notifications not yet enqueued.
+        mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
+    }
+
+    void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) {
+        String listenerName = listener == null ? null : listener.component.toShortString();
+        if (DBG) {
+            Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName));
+        }
+        mSnoozeHelper.repost(key);
+        savePolicyFile();
+    }
+
+    @GuardedBy("mNotificationLock")
+    void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
+            ManagedServiceInfo listener, boolean includeCurrentProfiles) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (mNotificationLock) {
+                    String listenerName =
+                            listener == null ? null : listener.component.toShortString();
+                    EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+                            null, userId, 0, 0, reason, listenerName);
+
+                    FlagChecker flagChecker = (int flags) -> {
+                        if ((flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR))
+                                != 0) {
+                            return false;
+                        }
+                        return true;
+                    };
+
+                    cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+                            null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
+                            includeCurrentProfiles, userId, true /*sendDelete*/, reason,
+                            listenerName, true);
+                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
+                            callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+                            flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
+                            reason, listenerName, false);
+                    mSnoozeHelper.cancel(userId, includeCurrentProfiles);
+                }
+            }
+        });
     }
 
     // Warning: The caller is responsible for invoking updateLightsLocked().
+    @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
-            String listenerName, int reason, boolean sendDelete) {
+            String listenerName, boolean sendDelete, FlagChecker flagChecker) {
         Notification n = r.getNotification();
         if (!n.isGroupSummary()) {
             return;
         }
 
         String pkg = r.sbn.getPackageName();
-        int userId = r.getUserId();
 
         if (pkg == null) {
             if (DBG) Log.e(TAG, "No package for group summary: " + r.getKey());
             return;
         }
 
-        final int N = mNotificationList.size();
-        for (int i = N - 1; i >= 0; i--) {
-            NotificationRecord childR = mNotificationList.get(i);
-            StatusBarNotification childSbn = childR.sbn;
+        cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
+                sendDelete, true, flagChecker);
+        cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
+                listenerName, sendDelete, false, flagChecker);
+    }
+
+    @GuardedBy("mNotificationLock")
+    private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
+            NotificationRecord parentNotification, int callingUid, int callingPid,
+            String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) {
+        final String pkg = parentNotification.sbn.getPackageName();
+        final int userId = parentNotification.getUserId();
+        final int reason = REASON_GROUP_SUMMARY_CANCELED;
+        for (int i = notificationList.size() - 1; i >= 0; i--) {
+            final NotificationRecord childR = notificationList.get(i);
+            final StatusBarNotification childSbn = childR.sbn;
             if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
-                    childR.getGroupKey().equals(r.getGroupKey())
-                    && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
+                    childR.getGroupKey().equals(parentNotification.getGroupKey())
+                    && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0
+                    && (flagChecker == null || flagChecker.apply(childR.getFlags()))) {
                 EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
                         childSbn.getTag(), userId, 0, 0, reason, listenerName);
-                mNotificationList.remove(i);
-                cancelNotificationLocked(childR, sendDelete, reason);
+                notificationList.remove(i);
+                mNotificationsByKey.remove(childR.getKey());
+                cancelNotificationLocked(childR, sendDelete, reason, wasPosted, listenerName);
             }
         }
     }
 
-    // lock on mNotificationList
+    @GuardedBy("mNotificationLock")
     void updateLightsLocked()
     {
         // handle notification lights
@@ -3618,47 +5024,97 @@ public class NotificationManagerService extends SystemService {
         // Don't flash while we are in a call or screen is on
         if (ledNotification == null || mInCall || mScreenOn) {
             mNotificationLight.turnOff();
-            if (mStatusBar != null) {
-                mStatusBar.notificationLightOff();
-            }
         } else {
-            final Notification ledno = ledNotification.sbn.getNotification();
-            int ledARGB = ledno.ledARGB;
-            int ledOnMS = ledno.ledOnMS;
-            int ledOffMS = ledno.ledOffMS;
-            if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) {
-                ledARGB = mDefaultNotificationColor;
-                ledOnMS = mDefaultNotificationLedOn;
-                ledOffMS = mDefaultNotificationLedOff;
-            }
-            if (mNotificationPulseEnabled) {
+            NotificationRecord.Light light = ledNotification.getLight();
+            if (light != null && mNotificationPulseEnabled) {
                 // pulse repeatedly
-                mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED,
-                        ledOnMS, ledOffMS);
+                mNotificationLight.setFlashing(light.color, Light.LIGHT_FLASH_TIMED,
+                        light.onMs, light.offMs);
             }
-            if (mStatusBar != null) {
-                // let SystemUI make an independent decision
-                mStatusBar.notificationLightPulse(ledARGB, ledOnMS, ledOffMS);
+        }
+    }
+
+    @GuardedBy("mNotificationLock")
+    @NonNull List<NotificationRecord> findGroupNotificationsLocked(String pkg,
+            String groupKey, int userId) {
+        List<NotificationRecord> records = new ArrayList<>();
+        records.addAll(findGroupNotificationByListLocked(mNotificationList, pkg, groupKey, userId));
+        records.addAll(
+                findGroupNotificationByListLocked(mEnqueuedNotifications, pkg, groupKey, userId));
+        return records;
+    }
+
+
+    @GuardedBy("mNotificationLock")
+    private @NonNull List<NotificationRecord> findGroupNotificationByListLocked(
+            ArrayList<NotificationRecord> list, String pkg, String groupKey, int userId) {
+        List<NotificationRecord> records = new ArrayList<>();
+        final int len = list.size();
+        for (int i = 0; i < len; i++) {
+            NotificationRecord r = list.get(i);
+            if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey)
+                    && r.sbn.getPackageName().equals(pkg)) {
+                records.add(r);
             }
         }
+        return records;
     }
 
-    // lock on mNotificationList
-    int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
-    {
-        ArrayList<NotificationRecord> list = mNotificationList;
+    // Searches both enqueued and posted notifications by key.
+    // TODO: need to combine a bunch of these getters with slightly different behavior.
+    // TODO: Should enqueuing just add to mNotificationsByKey instead?
+    @GuardedBy("mNotificationLock")
+    private NotificationRecord findNotificationByKeyLocked(String key) {
+        NotificationRecord r;
+        if ((r = findNotificationByListLocked(mNotificationList, key)) != null) {
+            return r;
+        }
+        if ((r = findNotificationByListLocked(mEnqueuedNotifications, key)) != null) {
+            return r;
+        }
+        return null;
+    }
+
+    @GuardedBy("mNotificationLock")
+    NotificationRecord findNotificationLocked(String pkg, String tag, int id, int userId) {
+        NotificationRecord r;
+        if ((r = findNotificationByListLocked(mNotificationList, pkg, tag, id, userId)) != null) {
+            return r;
+        }
+        if ((r = findNotificationByListLocked(mEnqueuedNotifications, pkg, tag, id, userId))
+                != null) {
+            return r;
+        }
+        return null;
+    }
+
+    @GuardedBy("mNotificationLock")
+    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
+            String pkg, String tag, int id, int userId) {
         final int len = list.size();
-        for (int i=0; i<len; i++) {
+        for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
             if (notificationMatchesUserId(r, userId) && r.sbn.getId() == id &&
                     TextUtils.equals(r.sbn.getTag(), tag) && r.sbn.getPackageName().equals(pkg)) {
-                return i;
+                return r;
             }
         }
-        return -1;
+        return null;
+    }
+
+    @GuardedBy("mNotificationLock")
+    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
+            String key) {
+        final int N = list.size();
+        for (int i = 0; i < N; i++) {
+            if (key.equals(list.get(i).getKey())) {
+                return list.get(i);
+            }
+        }
+        return null;
     }
 
-    // lock on mNotificationList
+    @GuardedBy("mNotificationLock")
     int indexOfNotificationLocked(String key) {
         final int N = mNotificationList.size();
         for (int i = 0; i < N; i++) {
@@ -3670,44 +5126,78 @@ public class NotificationManagerService extends SystemService {
     }
 
     private void updateNotificationPulse() {
-        synchronized (mNotificationList) {
+        synchronized (mNotificationLock) {
             updateLightsLocked();
         }
     }
 
-    private static boolean isUidSystem(int uid) {
+    protected boolean isCallingUidSystem() {
+        final int uid = Binder.getCallingUid();
+        return uid == Process.SYSTEM_UID;
+    }
+
+    protected boolean isUidSystemOrPhone(int uid) {
         final int appid = UserHandle.getAppId(uid);
         return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
     }
 
-    private static boolean isCallerSystem() {
-        return isUidSystem(Binder.getCallingUid());
+    // TODO: Most calls should probably move to isCallerSystem.
+    protected boolean isCallerSystemOrPhone() {
+        return isUidSystemOrPhone(Binder.getCallingUid());
+    }
+
+    private void checkCallerIsSystemOrShell() {
+        if (Binder.getCallingUid() == Process.SHELL_UID) {
+            return;
+        }
+        checkCallerIsSystem();
     }
 
-    private static void checkCallerIsSystem() {
-        if (isCallerSystem()) {
+    private void checkCallerIsSystem() {
+        if (isCallerSystemOrPhone()) {
             return;
         }
         throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
     }
 
-    private static void checkCallerIsSystemOrSameApp(String pkg) {
-        if (isCallerSystem()) {
+    private void checkCallerIsSystemOrSameApp(String pkg) {
+        if (isCallerSystemOrPhone()) {
             return;
         }
         checkCallerIsSameApp(pkg);
     }
 
-    private static void checkCallerIsSameApp(String pkg) {
+    private boolean isCallerInstantApp(String pkg) {
+        // System is always allowed to act for ephemeral apps.
+        if (isCallerSystemOrPhone()) {
+            return false;
+        }
+
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+
+        try {
+            ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0,
+                    UserHandle.getCallingUserId());
+            if (ai == null) {
+                throw new SecurityException("Unknown package " + pkg);
+            }
+            return ai.isInstantApp();
+        } catch (RemoteException re) {
+            throw new SecurityException("Unknown package " + pkg, re);
+        }
+
+    }
+
+    private void checkCallerIsSameApp(String pkg) {
         final int uid = Binder.getCallingUid();
         try {
-            ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+            ApplicationInfo ai = mPackageManager.getApplicationInfo(
                     pkg, 0, UserHandle.getCallingUserId());
             if (ai == null) {
                 throw new SecurityException("Unknown package " + pkg);
             }
             if (!UserHandle.isSameApp(ai.uid, uid)) {
-                throw new SecurityException("Calling uid " + uid + " gave package"
+                throw new SecurityException("Calling uid " + uid + " gave package "
                         + pkg + " which is owned by uid " + ai.uid);
             }
         } catch (RemoteException re) {
@@ -3738,9 +5228,8 @@ public class NotificationManagerService extends SystemService {
     /**
      * Generates a NotificationRankingUpdate from 'sbns', considering only
      * notifications visible to the given listener.
-     *
-     * <p>Caller must hold a lock on mNotificationList.</p>
      */
+    @GuardedBy("mNotificationLock")
     private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {
         final int N = mNotificationList.size();
         ArrayList<String> keys = new ArrayList<String>(N);
@@ -3750,6 +5239,10 @@ public class NotificationManagerService extends SystemService {
         Bundle visibilityOverrides = new Bundle();
         Bundle suppressedVisualEffects = new Bundle();
         Bundle explanation = new Bundle();
+        Bundle channels = new Bundle();
+        Bundle overridePeople = new Bundle();
+        Bundle snoozeCriteria = new Bundle();
+        Bundle showBadge = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -3771,6 +5264,10 @@ public class NotificationManagerService extends SystemService {
                 visibilityOverrides.putInt(key, record.getPackageVisibilityOverride());
             }
             overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey());
+            channels.putParcelable(key, record.getChannel());
+            overridePeople.putStringArrayList(key, record.getPeopleOverride());
+            snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
+            showBadge.putBoolean(key, record.canShowBadge());
         }
         final int M = keys.size();
         String[] keysAr = keys.toArray(new String[M]);
@@ -3780,7 +5277,40 @@ public class NotificationManagerService extends SystemService {
             importanceAr[i] = importance.get(i);
         }
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
-                suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys);
+                suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
+                channels, overridePeople, snoozeCriteria, showBadge);
+    }
+
+    boolean hasCompanionDevice(ManagedServiceInfo info) {
+        if (mCompanionManager == null) {
+            mCompanionManager = getCompanionManager();
+        }
+        // Companion mgr doesn't exist on all device types
+        if (mCompanionManager == null) {
+            return false;
+        }
+        long identity = Binder.clearCallingIdentity();
+        try {
+            List<String> associations = mCompanionManager.getAssociations(
+                    info.component.getPackageName(), info.userid);
+            if (!ArrayUtils.isEmpty(associations)) {
+                return true;
+            }
+        } catch (SecurityException se) {
+            // Not a privileged listener
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Cannot reach companion device service", re);
+        } catch (Exception e) {
+            Slog.e(TAG, "Cannot verify listener " + info, e);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return false;
+    }
+
+    protected ICompanionDeviceManager getCompanionManager() {
+        return ICompanionDeviceManager.Stub.asInterface(
+                ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
     }
 
     private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
@@ -3794,7 +5324,7 @@ public class NotificationManagerService extends SystemService {
     private boolean isPackageSuspendedForUser(String pkg, int uid) {
         int userId = UserHandle.getUserId(uid);
         try {
-            return AppGlobals.getPackageManager().isPackageSuspendedForUser(pkg, userId);
+            return mPackageManager.isPackageSuspendedForUser(pkg, userId);
         } catch (RemoteException re) {
             throw new SecurityException("Could not talk to package manager service");
         } catch (IllegalArgumentException ex) {
@@ -3827,19 +5357,21 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
-    public class NotificationRankers extends ManagedServices {
+    public class NotificationAssistants extends ManagedServices {
+        static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
 
-        public NotificationRankers() {
-            super(getContext(), mHandler, mNotificationList, mUserProfiles);
+        public NotificationAssistants(IPackageManager pm) {
+            super(getContext(), mNotificationLock, mUserProfiles, pm);
         }
 
         @Override
         protected Config getConfig() {
             Config c = new Config();
-            c.caption = "notification ranker service";
-            c.serviceInterface = NotificationRankerService.SERVICE_INTERFACE;
-            c.secureSettingName = null;
-            c.bindPermission = Manifest.permission.BIND_NOTIFICATION_RANKER_SERVICE;
+            c.caption = "notification assistant service";
+            c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
+            c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
+            c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
+            c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
             c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
             c.clientLabel = R.string.notification_ranker_binding_label;
             return c;
@@ -3861,6 +5393,7 @@ public class NotificationManagerService extends SystemService {
         }
 
         @Override
+        @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             mListeners.unregisterService(removed.service, removed.userid);
         }
@@ -3869,10 +5402,9 @@ public class NotificationManagerService extends SystemService {
             final StatusBarNotification sbn = r.sbn;
             TrimCache trimCache = new TrimCache(sbn);
 
-            // mServices is the list inside ManagedServices of all the rankers,
             // There should be only one, but it's a list, so while we enforce
             // singularity elsewhere, we keep it general here, to avoid surprises.
-            for (final ManagedServiceInfo info : NotificationRankers.this.mServices) {
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 boolean sbnVisible = isVisibleToListener(sbn, info);
                 if (!sbnVisible) {
                     continue;
@@ -3884,84 +5416,64 @@ public class NotificationManagerService extends SystemService {
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        notifyEnqueued(info, sbnToPost, importance, fromUser);
+                        notifyEnqueued(info, sbnToPost);
                     }
                 });
             }
         }
 
         private void notifyEnqueued(final ManagedServiceInfo info,
-                final StatusBarNotification sbn, int importance, boolean fromUser) {
-            final INotificationListener ranker = (INotificationListener) info.service;
+                final StatusBarNotification sbn) {
+            final INotificationListener assistant = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
-                ranker.onNotificationEnqueued(sbnHolder, importance, fromUser);
+                assistant.onNotificationEnqueued(sbnHolder);
             } catch (RemoteException ex) {
-                Log.e(TAG, "unable to notify ranker (enqueued): " + ranker, ex);
-            }
-        }
-
-        public boolean isEnabled() {
-            return !mServices.isEmpty();
-        }
-
-        @Override
-        public void onUserSwitched(int user) {
-            synchronized (mNotificationList) {
-                int i = mServices.size()-1;
-                while (i --> 0) {
-                    final ManagedServiceInfo info = mServices.get(i);
-                    unregisterService(info.service, info.userid);
-                }
+                Log.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
             }
-            registerRanker();
         }
 
-        @Override
-        public void onPackagesChanged(boolean removingPackage, String[] pkgList) {
-            if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
-                    + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)));
-            if (mRankerServicePackageName == null) {
-                return;
-            }
-
-            if (pkgList != null && (pkgList.length > 0) && !removingPackage) {
-                for (String pkgName : pkgList) {
-                    if (mRankerServicePackageName.equals(pkgName)) {
-                        registerRanker();
+        /**
+         * asynchronously notify the assistant that a notification has been snoozed until a
+         * context
+         */
+        @GuardedBy("mNotificationLock")
+        public void notifyAssistantSnoozedLocked(final StatusBarNotification sbn,
+                final String snoozeCriterionId) {
+            TrimCache trimCache = new TrimCache(sbn);
+            for (final ManagedServiceInfo info : getServices()) {
+                final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        final INotificationListener assistant =
+                                (INotificationListener) info.service;
+                        StatusBarNotificationHolder sbnHolder
+                                = new StatusBarNotificationHolder(sbnToPost);
+                        try {
+                            assistant.onNotificationSnoozedUntilContext(
+                                    sbnHolder, snoozeCriterionId);
+                        } catch (RemoteException ex) {
+                            Log.e(TAG, "unable to notify assistant (snoozed): " + assistant, ex);
+                        }
                     }
-                }
+                });
             }
         }
 
-        protected void registerRanker() {
-            // Find the updatable ranker and register it.
-            if (mRankerServicePackageName == null) {
-                Slog.w(TAG, "could not start ranker service: no package specified!");
-                return;
-            }
-            Set<ComponentName> rankerComponents = queryPackageForServices(
-                    mRankerServicePackageName, UserHandle.USER_SYSTEM);
-            Iterator<ComponentName> iterator = rankerComponents.iterator();
-            if (iterator.hasNext()) {
-                ComponentName rankerComponent = iterator.next();
-                if (iterator.hasNext()) {
-                    Slog.e(TAG, "found multiple ranker services:" + rankerComponents);
-                } else {
-                    registerSystemService(rankerComponent, UserHandle.USER_SYSTEM);
-                }
-            } else {
-                Slog.w(TAG, "could not start ranker service: none found");
-            }
+        public boolean isEnabled() {
+            return !getServices().isEmpty();
         }
     }
 
     public class NotificationListeners extends ManagedServices {
+        static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
 
         private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
 
-        public NotificationListeners() {
-            super(getContext(), mHandler, mNotificationList, mUserProfiles);
+        public NotificationListeners(IPackageManager pm) {
+            super(getContext(), mNotificationLock, mUserProfiles, pm);
+
         }
 
         @Override
@@ -3969,6 +5481,7 @@ public class NotificationManagerService extends SystemService {
             Config c = new Config();
             c.caption = "notification listener";
             c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
+            c.xmlTag = TAG_ENABLED_NOTIFICATION_LISTENERS;
             c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
             c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
             c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
@@ -3990,7 +5503,7 @@ public class NotificationManagerService extends SystemService {
         public void onServiceAdded(ManagedServiceInfo info) {
             final INotificationListener listener = (INotificationListener) info.service;
             final NotificationRankingUpdate update;
-            synchronized (mNotificationList) {
+            synchronized (mNotificationLock) {
                 update = makeRankingUpdateLocked(info);
             }
             try {
@@ -4001,6 +5514,7 @@ public class NotificationManagerService extends SystemService {
         }
 
         @Override
+        @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             if (removeDisabledHints(removed)) {
                 updateListenerHintsLocked();
@@ -4009,6 +5523,7 @@ public class NotificationManagerService extends SystemService {
             mLightTrimListeners.remove(removed);
         }
 
+        @GuardedBy("mNotificationLock")
         public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
             if (trim == TRIM_LIGHT) {
                 mLightTrimListeners.add(info);
@@ -4028,11 +5543,12 @@ public class NotificationManagerService extends SystemService {
          * Also takes care of removing a notification that has been visible to a listener before,
          * but isn't anymore.
          */
+        @GuardedBy("mNotificationLock")
         public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
             // Lazily initialized snapshots of the notification.
             TrimCache trimCache = new TrimCache(sbn);
 
-            for (final ManagedServiceInfo info : mServices) {
+            for (final ManagedServiceInfo info : getServices()) {
                 boolean sbnVisible = isVisibleToListener(sbn, info);
                 boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
                 // This notification hasn't been and still isn't visible -> ignore.
@@ -4047,7 +5563,7 @@ public class NotificationManagerService extends SystemService {
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            notifyRemoved(info, oldSbnLightClone, update);
+                            notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED);
                         }
                     });
                     continue;
@@ -4066,12 +5582,13 @@ public class NotificationManagerService extends SystemService {
         /**
          * asynchronously notify all listeners about a removed notification
          */
-        public void notifyRemovedLocked(StatusBarNotification sbn) {
+        @GuardedBy("mNotificationLock")
+        public void notifyRemovedLocked(StatusBarNotification sbn, int reason) {
             // make a copy in case changes are made to the underlying Notification object
             // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
             // notification
             final StatusBarNotification sbnLight = sbn.cloneLight();
-            for (final ManagedServiceInfo info : mServices) {
+            for (final ManagedServiceInfo info : getServices()) {
                 if (!isVisibleToListener(sbn, info)) {
                     continue;
                 }
@@ -4079,7 +5596,7 @@ public class NotificationManagerService extends SystemService {
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        notifyRemoved(info, sbnLight, update);
+                        notifyRemoved(info, sbnLight, update, reason);
                     }
                 });
             }
@@ -4088,8 +5605,9 @@ public class NotificationManagerService extends SystemService {
         /**
          * asynchronously notify all listeners about a reordering of notifications
          */
+        @GuardedBy("mNotificationLock")
         public void notifyRankingUpdateLocked() {
-            for (final ManagedServiceInfo serviceInfo : mServices) {
+            for (final ManagedServiceInfo serviceInfo : getServices()) {
                 if (!serviceInfo.isEnabledForCurrentProfiles()) {
                     continue;
                 }
@@ -4103,8 +5621,9 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
+        @GuardedBy("mNotificationLock")
         public void notifyListenerHintsChangedLocked(final int hints) {
-            for (final ManagedServiceInfo serviceInfo : mServices) {
+            for (final ManagedServiceInfo serviceInfo : getServices()) {
                 if (!serviceInfo.isEnabledForCurrentProfiles()) {
                     continue;
                 }
@@ -4118,7 +5637,7 @@ public class NotificationManagerService extends SystemService {
         }
 
         public void notifyInterruptionFilterChanged(final int interruptionFilter) {
-            for (final ManagedServiceInfo serviceInfo : mServices) {
+            for (final ManagedServiceInfo serviceInfo : getServices()) {
                 if (!serviceInfo.isEnabledForCurrentProfiles()) {
                     continue;
                 }
@@ -4131,9 +5650,48 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
+        protected void notifyNotificationChannelChanged(final String pkg, final UserHandle user,
+                final NotificationChannel channel, final int modificationType) {
+            if (channel == null) {
+                return;
+            }
+            for (final ManagedServiceInfo serviceInfo : getServices()) {
+                if (!serviceInfo.enabledAndUserMatches(UserHandle.getCallingUserId())) {
+                    continue;
+                }
+
+                BackgroundThread.getHandler().post(() -> {
+                    if (hasCompanionDevice(serviceInfo)) {
+                        notifyNotificationChannelChanged(
+                                serviceInfo, pkg, user, channel, modificationType);
+                    }
+                });
+            }
+        }
+
+        protected void notifyNotificationChannelGroupChanged(
+                final String pkg, final UserHandle user, final NotificationChannelGroup group,
+                final int modificationType) {
+            if (group == null) {
+                return;
+            }
+            for (final ManagedServiceInfo serviceInfo : getServices()) {
+                if (!serviceInfo.enabledAndUserMatches(UserHandle.getCallingUserId())) {
+                    continue;
+                }
+
+                BackgroundThread.getHandler().post(() -> {
+                    if (hasCompanionDevice(serviceInfo)) {
+                        notifyNotificationChannelGroupChanged(
+                                serviceInfo, pkg, user, group, modificationType);
+                    }
+                });
+            }
+        }
+
         private void notifyPosted(final ManagedServiceInfo info,
                 final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
-            final INotificationListener listener = (INotificationListener)info.service;
+            final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
                 listener.onNotificationPosted(sbnHolder, rankingUpdate);
@@ -4143,14 +5701,14 @@ public class NotificationManagerService extends SystemService {
         }
 
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
-                NotificationRankingUpdate rankingUpdate) {
+                NotificationRankingUpdate rankingUpdate, int reason) {
             if (!info.enabledAndUserMatches(sbn.getUserId())) {
                 return;
             }
             final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
-                listener.onNotificationRemoved(sbnHolder, rankingUpdate);
+                listener.onNotificationRemoved(sbnHolder, rankingUpdate, reason);
             } catch (RemoteException ex) {
                 Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
             }
@@ -4185,13 +5743,35 @@ public class NotificationManagerService extends SystemService {
             }
         }
 
-        private boolean isListenerPackage(String packageName) {
+        void notifyNotificationChannelChanged(ManagedServiceInfo info,
+                final String pkg, final UserHandle user, final NotificationChannel channel,
+                final int modificationType) {
+            final INotificationListener listener = (INotificationListener) info.service;
+            try {
+                listener.onNotificationChannelModification(pkg, user, channel, modificationType);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to notify listener (channel changed): " + listener, ex);
+            }
+        }
+
+        private void notifyNotificationChannelGroupChanged(ManagedServiceInfo info,
+                final String pkg, final UserHandle user, final NotificationChannelGroup group,
+                final int modificationType) {
+            final INotificationListener listener = (INotificationListener) info.service;
+            try {
+                listener.onNotificationChannelGroupModification(pkg, user, group, modificationType);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to notify listener (channel group changed): " + listener, ex);
+            }
+        }
+
+        public boolean isListenerPackage(String packageName) {
             if (packageName == null) {
                 return false;
             }
             // TODO: clean up locking object later
-            synchronized (mNotificationList) {
-                for (final ManagedServiceInfo serviceInfo : mServices) {
+            synchronized (mNotificationLock) {
+                for (final ManagedServiceInfo serviceInfo : getServices()) {
                     if (packageName.equals(serviceInfo.component.getPackageName())) {
                         return true;
                     }
@@ -4208,11 +5788,15 @@ public class NotificationManagerService extends SystemService {
         public long since;
         public boolean stats;
         public boolean redact = true;
+        public boolean proto = false;
 
         public static DumpFilter parseFromArguments(String[] args) {
             final DumpFilter filter = new DumpFilter();
             for (int ai = 0; ai < args.length; ai++) {
                 final String a = args[ai];
+                if ("--proto".equals(args[0])) {
+                    filter.proto = true;
+                }
                 if ("--noredact".equals(a) || "--reveal".equals(a)) {
                     filter.redact = false;
                 } else if ("p".equals(a) || "pkg".equals(a) || "--package".equals(a)) {
@@ -4232,7 +5816,7 @@ public class NotificationManagerService extends SystemService {
                     filter.stats = true;
                     if (ai < args.length-1) {
                         ai++;
-                        filter.since = Long.valueOf(args[ai]);
+                        filter.since = Long.parseLong(args[ai]);
                     } else {
                         filter.since = 0;
                     }
@@ -4284,78 +5868,96 @@ public class NotificationManagerService extends SystemService {
         }
     }
 
-    private final class PolicyAccess {
-        private static final String SEPARATOR = ":";
-        private final String[] PERM = {
-            android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
-        };
-
-        public boolean isPackageGranted(String pkg) {
-            return pkg != null && getGrantedPackages().contains(pkg);
-        }
+    private class ShellCmd extends ShellCommand {
+        public static final String USAGE = "help\n"
+                + "allow_listener COMPONENT [user_id]\n"
+                + "disallow_listener COMPONENT [user_id]\n"
+                + "set_assistant COMPONENT\n"
+                + "remove_assistant COMPONENT\n"
+                + "allow_dnd PACKAGE\n"
+                + "disallow_dnd PACKAGE";
 
-        public void put(String pkg, boolean granted) {
-            if (pkg == null) return;
-            final ArraySet<String> pkgs = getGrantedPackages();
-            boolean changed;
-            if (granted) {
-                changed = pkgs.add(pkg);
-            } else {
-                changed = pkgs.remove(pkg);
+        @Override
+        public int onCommand(String cmd) {
+            if (cmd == null) {
+                return handleDefaultCommands(cmd);
             }
-            if (!changed) return;
-            final String setting = TextUtils.join(SEPARATOR, pkgs);
-            final int currentUser = ActivityManager.getCurrentUser();
-            Settings.Secure.putStringForUser(getContext().getContentResolver(),
-                    Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                    setting,
-                    currentUser);
-            getContext().sendBroadcastAsUser(new Intent(NotificationManager
-                    .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
-                .setPackage(pkg)
-                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), new UserHandle(currentUser), null);
-        }
-
-        public ArraySet<String> getGrantedPackages() {
-            final ArraySet<String> pkgs = new ArraySet<>();
-
-            long identity = Binder.clearCallingIdentity();
+            final PrintWriter pw = getOutPrintWriter();
             try {
-                final String setting = Settings.Secure.getStringForUser(
-                        getContext().getContentResolver(),
-                        Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                        ActivityManager.getCurrentUser());
-                if (setting != null) {
-                    final String[] tokens = setting.split(SEPARATOR);
-                    for (int i = 0; i < tokens.length; i++) {
-                        String token = tokens[i];
-                        if (token != null) {
-                            token = token.trim();
+                switch (cmd) {
+                    case "allow_dnd": {
+                        getBinderService().setNotificationPolicyAccessGranted(
+                                getNextArgRequired(), true);
+                    }
+                    break;
+
+                    case "disallow_dnd": {
+                        getBinderService().setNotificationPolicyAccessGranted(
+                                getNextArgRequired(), false);
+                    }
+                    break;
+                    case "allow_listener": {
+                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+                        if (cn == null) {
+                            pw.println("Invalid listener - must be a ComponentName");
+                            return -1;
+                        }
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), true);
+                        }
+                    }
+                    break;
+                    case "disallow_listener": {
+                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+                        if (cn == null) {
+                            pw.println("Invalid listener - must be a ComponentName");
+                            return -1;
+                        }
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), false);
                         }
-                        if (TextUtils.isEmpty(token)) {
-                            continue;
+                    }
+                    break;
+                    case "allow_assistant": {
+                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+                        if (cn == null) {
+                            pw.println("Invalid assistant - must be a ComponentName");
+                            return -1;
+                        }
+                        getBinderService().setNotificationAssistantAccessGranted(cn, true);
+                    }
+                    break;
+                    case "disallow_assistant": {
+                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
+                        if (cn == null) {
+                            pw.println("Invalid assistant - must be a ComponentName");
+                            return -1;
                         }
-                        pkgs.add(token);
+                        getBinderService().setNotificationAssistantAccessGranted(cn, false);
                     }
+                    break;
+
+                    default:
+                        return handleDefaultCommands(cmd);
                 }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+            } catch (Exception e) {
+                pw.println("Error occurred. Check logcat for details. " + e.getMessage());
+                Slog.e(TAG, "Error running shell command", e);
             }
-            return pkgs;
+            return 0;
         }
 
-        public String[] getRequestingPackages() throws RemoteException {
-            final ParceledListSlice list = AppGlobals.getPackageManager()
-                    .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
-                            ActivityManager.getCurrentUser());
-            final List<PackageInfo> pkgs = list.getList();
-            if (pkgs == null || pkgs.isEmpty()) return new String[0];
-            final int N = pkgs.size();
-            final String[] rt = new String[N];
-            for (int i = 0; i < N; i++) {
-                rt[i] = pkgs.get(i).packageName;
-            }
-            return rt;
+        @Override
+        public void onHelp() {
+            getOutPrintWriter().println(USAGE);
         }
     }
 }