Show snoozed conversations in conversation header

- And allow them to be unsnoozed with a tap.
- Also use the conversation's shortcut icon and name if available.
- And ignore the setting to turn off the strips since it now requires
explicit user action to make the strip visible

Note 1: unsnoozing a notification causes it to make sound again - we
probably want to change that for manually unsnoozed things
Note 2: the entries in the header don't yet persist across a reboot

Test: atest, manual
Bug: 149486431
Change-Id: Id661c25a49bc982e39deab977eb912f51eaf6757
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 6562572..0cd96b8 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -19,6 +19,7 @@
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -37,6 +38,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
@@ -53,6 +55,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.widget.RemoteViews;
 
 import com.android.internal.annotations.GuardedBy;
@@ -1566,6 +1569,7 @@
         private boolean mCanBubble;
         private boolean mVisuallyInterruptive;
         private boolean mIsConversation;
+        private ShortcutInfo mShortcutInfo;
 
         private static final int PARCEL_VERSION = 2;
 
@@ -1599,6 +1603,7 @@
             out.writeBoolean(mCanBubble);
             out.writeBoolean(mVisuallyInterruptive);
             out.writeBoolean(mIsConversation);
+            out.writeParcelable(mShortcutInfo, flags);
         }
 
         /** @hide */
@@ -1620,7 +1625,7 @@
             mImportance = in.readInt();
             mImportanceExplanation = in.readCharSequence(); // may be null
             mOverrideGroupKey = in.readString(); // may be null
-            mChannel = (NotificationChannel) in.readParcelable(cl); // may be null
+            mChannel = in.readParcelable(cl); // may be null
             mOverridePeople = in.createStringArrayList();
             mSnoozeCriteria = in.createTypedArrayList(SnoozeCriterion.CREATOR);
             mShowBadge = in.readBoolean();
@@ -1633,6 +1638,7 @@
             mCanBubble = in.readBoolean();
             mVisuallyInterruptive = in.readBoolean();
             mIsConversation = in.readBoolean();
+            mShortcutInfo = in.readParcelable(cl);
         }
 
 
@@ -1840,6 +1846,13 @@
         /**
          * @hide
          */
+        public @Nullable ShortcutInfo getShortcutInfo() {
+            return mShortcutInfo;
+        }
+
+        /**
+         * @hide
+         */
         @VisibleForTesting
         public void populate(String key, int rank, boolean matchesInterruptionFilter,
                 int visibilityOverride, int suppressedVisualEffects, int importance,
@@ -1849,7 +1862,7 @@
                 int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
                 boolean noisy, ArrayList<Notification.Action> smartActions,
                 ArrayList<CharSequence> smartReplies, boolean canBubble,
-                boolean visuallyInterruptive, boolean isConversation) {
+                boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1872,6 +1885,7 @@
             mCanBubble = canBubble;
             mVisuallyInterruptive = visuallyInterruptive;
             mIsConversation = isConversation;
+            mShortcutInfo = shortcutInfo;
         }
 
         /**
@@ -1898,7 +1912,8 @@
                     other.mSmartReplies,
                     other.mCanBubble,
                     other.mVisuallyInterruptive,
-                    other.mIsConversation);
+                    other.mIsConversation,
+                    other.mShortcutInfo);
         }
 
         /**
@@ -1952,7 +1967,10 @@
                     && Objects.equals(mSmartReplies, other.mSmartReplies)
                     && Objects.equals(mCanBubble, other.mCanBubble)
                     && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive)
-                    && Objects.equals(mIsConversation, other.mIsConversation);
+                    && Objects.equals(mIsConversation, other.mIsConversation)
+                    // Shortcutinfo doesn't have equals either; use id
+                    &&  Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
+                    (other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()));
         }
     }
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
index 6650c15..f79cd86 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
@@ -68,14 +68,14 @@
         public final String key;
         public final CharSequence name;
         public final Drawable avatar;
-        public final PendingIntent clickIntent;
+        public final Runnable clickRunnable;
 
         public PersonData(String key, CharSequence name, Drawable avatar,
-                PendingIntent clickIntent) {
+                Runnable clickRunnable) {
             this.key = key;
             this.name = name;
             this.avatar = avatar;
-            this.clickIntent = clickIntent;
+            this.clickRunnable = clickRunnable;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 924d16d..f01fa81 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -71,7 +71,8 @@
             public void onEntryRemoved(
                     NotificationEntry entry,
                     NotificationVisibility visibility,
-                    boolean removedByUser) {
+                    boolean removedByUser,
+                    int reason) {
                 removeNotification(entry.getSbn());
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a220dac..48457f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -425,7 +425,8 @@
                     public void onEntryRemoved(
                             NotificationEntry entry,
                             @android.annotation.Nullable NotificationVisibility visibility,
-                            boolean removedByUser) {
+                            boolean removedByUser,
+                            int reason) {
                         BubbleController.this.onEntryRemoved(entry);
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 352ee33..1ec979c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -30,6 +30,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
 import android.media.AudioManager;
@@ -167,6 +169,12 @@
         return LatencyTracker.getInstance(context);
     }
 
+    @Singleton
+    @Provides
+    static LauncherApps provideLauncherApps(Context context) {
+        return context.getSystemService(LauncherApps.class);
+    }
+
     @SuppressLint("MissingPermission")
     @Singleton
     @Provides
@@ -184,6 +192,12 @@
 
     @Singleton
     @Provides
+    static PackageManager providePackageManager(Context context) {
+        return context.getPackageManager();
+    }
+
+    @Singleton
+    @Provides
     static PackageManagerWrapper providePackageManagerWrapper() {
         return PackageManagerWrapper.getInstance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index b43fe73..047edd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
 
+import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.app.NotificationManager;
 import android.content.ComponentName;
@@ -161,6 +162,15 @@
         }
     }
 
+    public final void unsnoozeNotification(@NonNull String key) {
+        if (!isBound()) return;
+        try {
+            getNotificationInterface().unsnoozeNotificationFromSystemListener(mWrapper, key);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
     public void registerAsSystemService() {
         try {
             registerAsSystemService(mContext,
@@ -195,7 +205,8 @@
                     new ArrayList<>(),
                     false,
                     false,
-                    false
+                    false,
+                    null
             );
         }
         return ranking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index cdb2c53..3d7beea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -214,7 +214,8 @@
             public void onEntryRemoved(
                     NotificationEntry entry,
                     NotificationVisibility visibility,
-                    boolean removedByUser) {
+                    boolean removedByUser,
+                    int reason) {
                 onNotificationRemoved(entry.getKey());
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index ebc2fa6..bf28040 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -292,7 +292,8 @@
             public void onEntryRemoved(
                     @Nullable NotificationEntry entry,
                     NotificationVisibility visibility,
-                    boolean removedByUser) {
+                    boolean removedByUser,
+                    int reason) {
                 // We're removing the notification, the smart controller can forget about it.
                 mSmartReplyController.stopSending(entry);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 07cf9d9..df21f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -77,7 +77,8 @@
             public void onEntryRemoved(
                     NotificationEntry entry,
                     NotificationVisibility visibility,
-                    boolean removedByUser) {
+                    boolean removedByUser,
+                    int reason) {
                 stopAlerting(entry.getKey());
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index 25253a1..2beceb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -92,7 +92,8 @@
     default void onEntryRemoved(
             NotificationEntry entry,
             @Nullable NotificationVisibility visibility,
-            boolean removedByUser) {
+            boolean removedByUser,
+            int reason) {
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 5ebd368..7f0479c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -477,7 +477,7 @@
 
                 mLogger.logNotifRemoved(entry.getKey(), removedByUser);
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
-                    listener.onEntryRemoved(entry, visibility, removedByUser);
+                    listener.onEntryRemoved(entry, visibility, removedByUser, reason);
                 }
                 for (NotifCollectionListener listener : mNotifCollectionListeners) {
                     // NEM doesn't have a good knowledge of reasons so defaulting to unknown.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
index f2765db..c9c6f28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
@@ -59,7 +59,8 @@
         public void onEntryRemoved(
                 NotificationEntry entry,
                 NotificationVisibility visibility,
-                boolean removedByUser) {
+                boolean removedByUser,
+                int reason) {
             mListContainer.cleanUpViewStateForEntry(entry);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 25a832d..3e9d8a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -153,6 +153,7 @@
     public String remoteInputMimeType;
     public Uri remoteInputUri;
     private Notification.BubbleMetadata mBubbleMetadata;
+    private ShortcutInfo mShortcutInfo;
 
     /**
      * If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index becb758..6e161c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -213,7 +213,8 @@
             public void onEntryRemoved(
                     NotificationEntry entry,
                     NotificationVisibility visibility,
-                    boolean removedByUser) {
+                    boolean removedByUser,
+                    int reason) {
                 if (removedByUser && visibility != null) {
                     logNotificationClear(entry.getKey(), entry.getSbn(), visibility);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
index 2a02c19..3007198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
@@ -47,7 +47,7 @@
     val key: PersonKey,
     val name: CharSequence,
     val avatar: Drawable,
-    val clickIntent: PendingIntent,
+    val clickRunnable: Runnable,
     val userId: Int
 )
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 721f32a..360bf96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,20 +17,30 @@
 package com.android.systemui.statusbar.notification.people
 
 import android.app.Notification
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutInfo
 import android.content.pm.UserInfo
+import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
 import android.os.UserManager
+import android.service.notification.NotificationListenerService
+import android.service.notification.NotificationListenerService.REASON_SNOOZED
 import android.service.notification.StatusBarNotification
+import android.util.IconDrawableFactory
 import android.util.SparseArray
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import com.android.internal.statusbar.NotificationVisibility
 import com.android.internal.widget.MessagingGroup
+import com.android.settingslib.notification.ConversationIconFactory
 import com.android.systemui.R
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NotificationPersonExtractorPlugin
+import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.notification.NotificationEntryListener
 import com.android.systemui.statusbar.notification.NotificationEntryManager
@@ -69,7 +79,7 @@
 
     override fun extractPerson(sbn: StatusBarNotification) =
             plugin?.extractPerson(sbn)?.run {
-                PersonModel(key, name, avatar, clickIntent, sbn.user.identifier)
+                PersonModel(key, name, avatar, clickRunnable, sbn.user.identifier)
             }
 
     override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
@@ -80,17 +90,26 @@
 
 @Singleton
 class PeopleHubDataSourceImpl @Inject constructor(
-    private val notificationEntryManager: NotificationEntryManager,
-    private val extractor: NotificationPersonExtractor,
-    private val userManager: UserManager,
-    @Background private val bgExecutor: Executor,
-    @Main private val mainExecutor: Executor,
-    private val notifLockscreenUserMgr: NotificationLockscreenUserManager
-) : DataSource<PeopleHubModel> {
+        private val notificationEntryManager: NotificationEntryManager,
+        private val extractor: NotificationPersonExtractor,
+        private val userManager: UserManager,
+        private val launcherApps: LauncherApps,
+        private val packageManager: PackageManager,
+        private val c: Context,
+        private val notificationListener: NotificationListener,
+        @Background private val bgExecutor: Executor,
+        @Main private val mainExecutor: Executor,
+        private val notifLockscreenUserMgr: NotificationLockscreenUserManager,
+        private val peopleNotificationIdentifier: PeopleNotificationIdentifier
+        ) : DataSource<PeopleHubModel> {
 
     private var userChangeSubscription: Subscription? = null
     private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
     private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
+    val context: Context = c.applicationContext
+    val iconFactory = ConversationIconFactory(context, launcherApps, packageManager,
+            IconDrawableFactory.newInstance(context), context.resources.getDimensionPixelSize(
+            R.dimen.notification_guts_conversation_icon_size))
 
     private val notificationEntryListener = object : NotificationEntryListener {
         override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry)
@@ -102,18 +121,23 @@
         override fun onEntryRemoved(
             entry: NotificationEntry,
             visibility: NotificationVisibility?,
-            removedByUser: Boolean
-        ) = removeVisibleEntry(entry)
+            removedByUser: Boolean,
+            reason: Int
+        ) = removeVisibleEntry(entry, reason)
     }
 
-    private fun removeVisibleEntry(entry: NotificationEntry) {
+    private fun removeVisibleEntry(entry: NotificationEntry, reason: Int) {
         (extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey())?.let { key ->
             val userId = entry.sbn.user.identifier
             bgExecutor.execute {
                 val parentId = userManager.getProfileParent(userId)?.id ?: userId
                 mainExecutor.execute {
-                    if (peopleHubManagerForUser[parentId]?.removeActivePerson(key) == true) {
-                        updateUi()
+                    if (reason == REASON_SNOOZED) {
+                        if (peopleHubManagerForUser[parentId]?.migrateActivePerson(key) == true) {
+                            updateUi()
+                        }
+                    } else {
+                        peopleHubManagerForUser[parentId]?.removeActivePerson(key)
                     }
                 }
             }
@@ -121,7 +145,7 @@
     }
 
     private fun addVisibleEntry(entry: NotificationEntry) {
-        (extractor.extractPerson(entry.sbn) ?: entry.extractPerson())?.let { personModel ->
+        entry.extractPerson()?.let { personModel ->
             val userId = entry.sbn.user.identifier
             bgExecutor.execute {
                 val parentId = userManager.getProfileParent(userId)?.id ?: userId
@@ -180,6 +204,34 @@
             listener.onDataChanged(model)
         }
     }
+
+    private fun NotificationEntry.extractPerson(): PersonModel? {
+        if (!peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)) {
+            return null
+        }
+        val clickRunnable = Runnable { notificationListener.unsnoozeNotification(key) }
+        val extras = sbn.notification.extras
+        val name = ranking.shortcutInfo?.shortLabel
+                ?: extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
+                ?: extras.getString(Notification.EXTRA_TITLE)
+                ?: return null
+        val drawable = ranking.shortcutInfo?.getIcon(iconFactory, sbn, ranking)
+                ?: iconFactory.getConversationDrawable(extractAvatarFromRow(this), sbn.packageName,
+                        sbn.uid, ranking.channel.isImportantConversation)
+
+        return PersonModel(key, name, drawable, clickRunnable, sbn.user.identifier)
+    }
+
+    private fun ShortcutInfo.getIcon(iconFactory: ConversationIconFactory,
+                                     sbn: StatusBarNotification,
+                                     ranking: NotificationListenerService.Ranking): Drawable? {
+        return iconFactory.getConversationDrawable(ranking.shortcutInfo, sbn.packageName, sbn.uid,
+                ranking.channel.isImportantConversation)
+    }
+
+    private fun NotificationEntry.extractPersonKey(): PersonKey? =
+            // TODO migrate to shortcut id when snoozing is conversation wide
+            if (peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)) key else null
 }
 
 private fun NotificationLockscreenUserManager.registerListener(
@@ -201,7 +253,7 @@
     // People that were once "active" and have been dismissed, and so can be displayed in the hub
     private val inactivePeople = ArrayDeque<PersonModel>(MAX_STORED_INACTIVE_PEOPLE)
 
-    fun removeActivePerson(key: PersonKey): Boolean {
+    fun migrateActivePerson(key: PersonKey): Boolean {
         activePeople.remove(key)?.let { data ->
             if (inactivePeople.size >= MAX_STORED_INACTIVE_PEOPLE) {
                 inactivePeople.removeLast()
@@ -212,6 +264,10 @@
         return false
     }
 
+    fun removeActivePerson(key: PersonKey) {
+        activePeople.remove(key)
+    }
+
     fun addActivePerson(person: PersonModel): Boolean {
         activePeople[person.key] = person
         return inactivePeople.removeIf { it.key == person.key }
@@ -229,19 +285,6 @@
 
 private fun ViewGroup.childrenWithId(id: Int): Sequence<View> = children.filter { it.id == id }
 
-private fun NotificationEntry.extractPerson(): PersonModel? {
-    if (!isMessagingNotification()) {
-        return null
-    }
-    val clickIntent = sbn.notification.contentIntent ?: return null
-    val extras = sbn.notification.extras
-    val name = extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
-            ?: extras.getString(Notification.EXTRA_TITLE)
-            ?: return null
-    val drawable = extractAvatarFromRow(this) ?: return null
-    return PersonModel(key, name, drawable, clickIntent, sbn.user.identifier)
-}
-
 fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
         entry.row
                 ?.childrenWithId(R.id.expanded)
@@ -261,8 +304,3 @@
                 }
                 ?.firstOrNull()
 
-private fun NotificationEntry.extractPersonKey(): PersonKey? =
-        if (isMessagingNotification()) key else null
-
-private fun NotificationEntry.isMessagingNotification() =
-        sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index a58c42b..62d3612 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -102,30 +102,19 @@
 @Singleton
 class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor(
     private val activityStarter: ActivityStarter,
-    private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>,
-    private val settingChangeSource: DataSource<@JvmSuppressWildcards Boolean>
+    private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
 ) : DataSource<PeopleHubViewModelFactory> {
 
     override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>): Subscription {
-        var stripEnabled = false
         var model: PeopleHubModel? = null
 
         fun updateListener() {
             // don't invoke listener until we've received our first model
             model?.let { model ->
-                val factory =
-                        if (stripEnabled) PeopleHubViewModelFactoryImpl(model, activityStarter)
-                        else EmptyViewModelFactory
+                val factory = PeopleHubViewModelFactoryImpl(model, activityStarter)
                 listener.onDataChanged(factory)
             }
         }
-
-        val settingSub = settingChangeSource.registerListener(object : DataListener<Boolean> {
-            override fun onDataChanged(data: Boolean) {
-                stripEnabled = data
-                updateListener()
-            }
-        })
         val dataSub = dataSource.registerListener(object : DataListener<PeopleHubModel> {
             override fun onDataChanged(data: PeopleHubModel) {
                 model = data
@@ -134,7 +123,6 @@
         })
         return object : Subscription {
             override fun unsubscribe() {
-                settingSub.unsubscribe()
                 dataSub.unsubscribe()
             }
         }
@@ -155,11 +143,7 @@
     override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
         val personViewModels = model.people.asSequence().map { personModel ->
             val onClick = {
-                activityStarter.startPendingIntentDismissingKeyguard(
-                        personModel.clickIntent,
-                        null,
-                        view
-                )
+                personModel.clickRunnable.run()
             }
             PersonViewModel(personModel.name, personModel.avatar, onClick)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index a5258fd..1088cdc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -216,15 +216,7 @@
         if (TextUtils.isEmpty(mConversationId)) {
             throw new IllegalArgumentException("Does not have required information");
         }
-        // TODO: consider querying this earlier in the notification pipeline and passing it in
-        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
-                .setPackage(mPackageName)
-                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED)
-                .setShortcutIds(Arrays.asList(mConversationId));
-        List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
-        if (shortcuts != null && !shortcuts.isEmpty()) {
-            mShortcutInfo = shortcuts.get(0);
-        }
+        mShortcutInfo = entry.getRanking().getShortcutInfo();
 
         mIsBubbleable = mEntry.getBubbleMetadata() != null
             && Settings.Global.getInt(mContext.getContentResolver(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 5703f06..8e192c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -172,7 +172,7 @@
 
         @Override
         public void onEntryRemoved(@Nullable NotificationEntry entry,
-                NotificationVisibility visibility, boolean removedByUser) {
+                NotificationVisibility visibility, boolean removedByUser, int reason) {
             updateLightsOutView();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index d709e02..8c31a37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -196,7 +196,8 @@
         public void onEntryRemoved(
                 @Nullable NotificationEntry entry,
                 NotificationVisibility visibility,
-                boolean removedByUser) {
+                boolean removedByUser,
+                int reason) {
             // Removes any alerts pending on this entry. Note that this will not stop any inflation
             // tasks started by a transfer, so this should only be used as clean-up for when
             // inflation is stopped and the pending alert no longer needs to happen.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 693cdd2..30d6b507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -199,7 +199,8 @@
                 public void onEntryRemoved(
                         @Nullable NotificationEntry entry,
                         NotificationVisibility visibility,
-                        boolean removedByUser) {
+                        boolean removedByUser,
+                        int reason) {
                     StatusBarNotificationPresenter.this.onNotificationRemoved(
                             entry.getKey(), entry.getSbn());
                     if (removedByUser) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
index 4d912de..b503183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
@@ -73,7 +73,7 @@
     private final NotificationEntryListener mInlineUriListener = new NotificationEntryListener() {
         @Override
         public void onEntryRemoved(NotificationEntry entry, NotificationVisibility visibility,
-                boolean removedByUser) {
+                boolean removedByUser, int reason) {
             try {
                 mStatusBarManagerService.clearInlineReplyUriPermissions(entry.getKey());
             } catch (RemoteException ex) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index c912b67..69e933e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui;
 
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.fail;
@@ -528,7 +530,8 @@
                         .setSbn(notification)
                         .build(),
                 null,
-                false);
+                false,
+                REASON_APP_CANCEL);
     }
 
     private void entryAdded(StatusBarNotification notification, int importance) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 3009593..9fe9e6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -685,7 +685,7 @@
         assertTrue(mBubbleController.hasBubbles());
 
         // Removes the notification
-        mEntryListener.onEntryRemoved(mRow.getEntry(), null, false);
+        mEntryListener.onEntryRemoved(mRow.getEntry(), null, false, REASON_APP_CANCEL);
         assertFalse(mBubbleController.hasBubbles());
     }
 
@@ -827,7 +827,7 @@
         mBubbleController.handleDismissalInterception(groupSummary.getEntry());
 
         // WHEN the summary is cancelled by the app
-        mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, true);
+        mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, false, REASON_APP_CANCEL);
 
         // THEN the summary and its children are removed from bubble data
         assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index 16f105d..fe8b89f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -20,6 +20,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager.Importance;
+import android.content.pm.ShortcutInfo;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.SnoozeCriterion;
 
@@ -53,6 +54,7 @@
     private boolean mCanBubble = false;
     private boolean mIsVisuallyInterruptive = false;
     private boolean mIsConversation = false;
+    private ShortcutInfo mShortcutInfo = null;
 
     public RankingBuilder() {
     }
@@ -79,6 +81,7 @@
         mCanBubble = ranking.canBubble();
         mIsVisuallyInterruptive = ranking.visuallyInterruptive();
         mIsConversation = ranking.isConversation();
+        mShortcutInfo = ranking.getShortcutInfo();
     }
 
     public Ranking build() {
@@ -104,7 +107,8 @@
                 mSmartReplies,
                 mCanBubble,
                 mIsVisuallyInterruptive,
-                mIsConversation);
+                mIsConversation,
+                mShortcutInfo);
         return ranking;
     }
 
@@ -189,6 +193,11 @@
         return this;
     }
 
+    public RankingBuilder setShortcutInfo(ShortcutInfo shortcutInfo) {
+        mShortcutInfo = shortcutInfo;
+        return this;
+    }
+
     public RankingBuilder setImportance(@Importance int importance) {
         mImportance = importance;
         return this;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 98f12ce..312bb7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -143,7 +143,7 @@
                     IMPORTANCE_DEFAULT,
                     null, null,
                     null, null, null, true, sentiment, false, -1, false, null, null, false, false,
-                    false);
+                    false, null);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
     }
@@ -162,7 +162,7 @@
                     null, null,
                     null, null, null, true,
                     Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
-                    false, smartActions, null, false, false, false);
+                    false, smartActions, null, false, false, false, null);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
     }
@@ -254,7 +254,7 @@
 
         verify(mPresenter).updateNotificationViews();
         verify(mEntryListener).onEntryRemoved(
-                eq(mEntry), any(), eq(false) /* removedByUser */);
+                eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
         verify(mRow).setRemoved();
 
         assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
@@ -266,7 +266,7 @@
         mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
 
         verify(mEntryListener, never()).onEntryRemoved(
-                eq(mEntry), any(), eq(false) /* removedByUser */);
+                eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
     }
 
     @Test
@@ -275,7 +275,7 @@
         mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
 
         verify(mEntryListener, never()).onEntryRemoved(
-                eq(mEntry), any(), eq(false /* removedByUser */));
+                eq(mEntry), any(), eq(false /* removedByUser */), eq(UNDEFINED_DISMISS_REASON));
     }
 
     @Test
@@ -356,7 +356,8 @@
         verify(extender).setShouldManageLifetime(mEntry, true);
         // THEN the notification is retained
         assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-        verify(mEntryListener, never()).onEntryRemoved(eq(mEntry), any(), eq(false));
+        verify(mEntryListener, never()).onEntryRemoved(
+                eq(mEntry), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
     }
 
     @Test
@@ -374,7 +375,8 @@
 
         // THEN the notification is removed
         assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-        verify(mEntryListener).onEntryRemoved(eq(mEntry), any(), eq(false));
+        verify(mEntryListener).onEntryRemoved(
+                eq(mEntry), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
     }
 
     @Test
@@ -447,7 +449,7 @@
         // THEN the interceptor intercepts & the entry is not removed & no listeners are called
         assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
         verify(mEntryListener, never()).onEntryRemoved(eq(mEntry),
-                any(NotificationVisibility.class), anyBoolean());
+                any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
     }
 
     @Test
@@ -466,7 +468,7 @@
         // THEN the interceptor intercepts & the entry is not removed & no listeners are called
         assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
         verify(mEntryListener, atLeastOnce()).onEntryRemoved(eq(mEntry),
-                any(NotificationVisibility.class), anyBoolean());
+                any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
     }
 
     private NotificationEntry createNotification() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 7343e5e..19dd027 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification;
 
+import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.verify;
 
@@ -87,7 +89,8 @@
         mEntryListener.onEntryRemoved(
                 entry,
                 NotificationVisibility.obtain(entry.getKey(), 0, 0, true),
-                false);
+                false,
+                UNDEFINED_DISMISS_REASON);
         verify(mListContainer).cleanUpViewStateForEntry(entry);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index d2bb011..92a9080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -21,6 +21,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
@@ -266,4 +267,9 @@
         mRankingBuilder.setSmartReplies(smartReplies);
         return this;
     }
+
+    public NotificationEntryBuilder setShortcutInfo(ShortcutInfo shortcutInfo) {
+        mRankingBuilder.setShortcutInfo(shortcutInfo);
+        return this;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index b0ca943..b1288f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -101,15 +101,13 @@
 
     @Test
     fun testViewModelDataSourceTransformsModel() {
-        val fakeClickIntent = PendingIntent.getActivity(context, 0, Intent("action"), 0)
-        val fakePerson = fakePersonModel("id", "name", fakeClickIntent)
+        val fakeClickRunnable = mock(Runnable::class.java)
+        val fakePerson = fakePersonModel("id", "name", fakeClickRunnable)
         val fakeModel = PeopleHubModel(listOf(fakePerson))
         val fakeModelDataSource = FakeDataSource(fakeModel)
-        val fakeSettingDataSource = FakeDataSource(true)
         val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
                 mockActivityStarter,
-                fakeModelDataSource,
-                fakeSettingDataSource
+                fakeModelDataSource
         )
         val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
         val mockClickView = mock(View::class.java)
@@ -126,35 +124,7 @@
 
         people[0].onClick()
 
-        verify(mockActivityStarter).startPendingIntentDismissingKeyguard(
-                same(fakeClickIntent),
-                any(),
-                same(mockClickView)
-        )
-    }
-
-    @Test
-    fun testViewModelDataSource_notVisibleIfSettingDisabled() {
-        val fakeClickIntent = PendingIntent.getActivity(context, 0, Intent("action"), 0)
-        val fakePerson = fakePersonModel("id", "name", fakeClickIntent)
-        val fakeModel = PeopleHubModel(listOf(fakePerson))
-        val fakeModelDataSource = FakeDataSource(fakeModel)
-        val fakeSettingDataSource = FakeDataSource(false)
-        val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
-                mockActivityStarter,
-                fakeModelDataSource,
-                fakeSettingDataSource
-        )
-        val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
-        val mockClickView = mock(View::class.java)
-
-        factoryDataSource.registerListener(fakeListener)
-
-        val viewModel = (fakeListener.lastSeen as Maybe.Just).value
-                .createWithAssociatedClickView(mockClickView)
-        assertThat(viewModel.isVisible).isFalse()
-        val people = viewModel.people.toList()
-        assertThat(people.size).isEqualTo(0)
+        verify(fakeClickRunnable).run()
     }
 }
 
@@ -178,10 +148,10 @@
 private fun fakePersonModel(
     id: String,
     name: CharSequence,
-    clickIntent: PendingIntent,
+    clickRunnable: Runnable,
     userId: Int = 0
 ): PersonModel =
-        PersonModel(id, name, mock(Drawable::class.java), clickIntent, userId)
+        PersonModel(id, name, mock(Drawable::class.java), clickRunnable, userId)
 
 private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
         PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 6c12c76..e1ab33a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -198,7 +198,7 @@
                 .build();
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 notification, UserHandle.CURRENT, null, 0);
-        mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+        mEntry = new NotificationEntryBuilder().setSbn(mSbn).setShortcutInfo(mShortcutInfo).build();
 
         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
                 new Intent(mContext, BubblesTestActivity.class), 0);
@@ -207,7 +207,10 @@
                         .createIntentBubble(bubbleIntent,
                                 Icon.createWithResource(mContext, R.drawable.android)).build())
                 .build();
-        mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
+        mBubbleEntry = new NotificationEntryBuilder()
+                .setSbn(mBubbleSbn)
+                .setShortcutInfo(mShortcutInfo)
+                .build();
 
         mConversationChannel = new NotificationChannel(
                 TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 48a3b25..5d0349d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -283,7 +283,8 @@
                 null,
                 false,
                 false,
-                false);
+                false,
+                null);
         mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
 
         TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index 3d59d61..dbb4512 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 
 import static junit.framework.Assert.assertFalse;
@@ -205,7 +206,8 @@
         // WHEN all active notifications are removed
         when(mEntryManager.hasActiveNotifications()).thenReturn(false);
         assertFalse(mLightsOutNotifController.shouldShowDot());
-        mEntryListener.onEntryRemoved(mock(NotificationEntry.class), null, false);
+        mEntryListener.onEntryRemoved(
+                mock(NotificationEntry.class), null, false, REASON_CANCEL_ALL);
 
         // THEN we shouldn't see the dot view
         assertIsShowingDot(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index e171a28..f6a099d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertFalse;
@@ -254,7 +255,8 @@
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
 
-        mNotificationEntryListener.onEntryRemoved(childEntry, null, false);
+        mNotificationEntryListener.onEntryRemoved(
+                childEntry, null, false, UNDEFINED_DISMISS_REASON);
 
         assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ceb1cd4..7a777c1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8575,7 +8575,8 @@
                     record.getSmartReplies(),
                     record.canBubble(),
                     record.isInterruptive(),
-                    record.isConversation()
+                    record.isConversation(),
+                    record.getShortcutInfo()
             );
             rankings.add(ranking);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 9d243e4..c07107f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1344,6 +1344,10 @@
         mShortcutInfo = shortcutInfo;
     }
 
+    public ShortcutInfo getShortcutInfo() {
+        return mShortcutInfo;
+    }
+
     /**
      * Whether this notification is a conversation notification.
      */
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index da0e03d..dc8d010 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -35,9 +35,12 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.PendingIntent;
+import android.app.Person;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.os.Binder;
@@ -118,6 +121,7 @@
             assertEquals(canBubble(i), ranking.canBubble());
             assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
             assertEquals(isConversation(i), ranking.isConversation());
+            assertEquals(getShortcutInfo(i).getId(), ranking.getShortcutInfo().getId());
         }
     }
 
@@ -186,7 +190,8 @@
                 (ArrayList) tweak.getSmartReplies(),
                 tweak.canBubble(),
                 tweak.visuallyInterruptive(),
-                tweak.isConversation()
+                tweak.isConversation(),
+                tweak.getShortcutInfo()
         );
         assertNotEquals(nru, nru2);
     }
@@ -264,7 +269,8 @@
                     getSmartReplies(key, i),
                     canBubble(i),
                     visuallyInterruptive(i),
-                    isConversation(i)
+                    isConversation(i),
+                    getShortcutInfo(i)
             );
             rankings[i] = ranking;
         }
@@ -377,6 +383,17 @@
         return index % 4 == 0;
     }
 
+    private ShortcutInfo getShortcutInfo(int index) {
+        ShortcutInfo si = new ShortcutInfo(
+                index, String.valueOf(index), "packageName", new ComponentName("1", "1"), null,
+                "title", 0, "titleResName", "text", 0, "textResName",
+                "disabledMessage", 0, "disabledMessageResName",
+                null, null, 0, null, 0, 0,
+                0, "iconResName", "bitmapPath", 0,
+                null, null);
+        return si;
+    }
+
     private void assertActionsEqual(
             List<Notification.Action> expecteds, List<Notification.Action> actuals) {
         assertEquals(expecteds.size(), actuals.size());
@@ -411,6 +428,7 @@
         assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
         assertEquals(comment, a.canBubble(), b.canBubble());
         assertEquals(comment, a.isConversation(), b.isConversation());
+        assertEquals(comment, a.getShortcutInfo().getId(), b.getShortcutInfo().getId());
         assertActionsEqual(a.getSmartActions(), b.getSmartActions());
     }