Merge Android 13 QPR1

Bug: 261731544
Merged-In: I230ead6bb84aa5918b881e83958c4fe70de0e2d4
Change-Id: I2b35167b6c28f5e90629e2585f54fe39671f9c47
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index dfb6ccb..bdf5253 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,6 +35,9 @@
     <!--Permission to start a voice interaction service-->
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE"/>
 
+    <!--Permission to query application package info-->
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
     <application android:name=".NotificationApplication">
         <activity android:name=".CarNotificationCenterActivity"
              android:theme="@style/Theme.DeviceDefault.NoActionBar.Notification"
diff --git a/OWNERS b/OWNERS
index 96b430f..cc3a03c 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,10 +1,10 @@
 # Default code reviewers picked from top 3 or more developers.
 # Please update this list if you find better candidates.
 cassieyw@google.com
+jainams@google.com
 alexstetson@google.com
 nehah@google.com
 abhijoy@google.com
 priyanksingh@google.com
 stenning@google.com
 igorr@google.com
-juliacr@google.com
diff --git a/src/com/android/car/notification/CarHeadsUpNotificationManager.java b/src/com/android/car/notification/CarHeadsUpNotificationManager.java
index 2bd5dca..f50fd37 100644
--- a/src/com/android/car/notification/CarHeadsUpNotificationManager.java
+++ b/src/com/android/car/notification/CarHeadsUpNotificationManager.java
@@ -382,18 +382,20 @@
         mRegisteredViewTreeListeners.put(currentNotification,
                 new Pair<>(onComputeInternalInsetsListener, onGlobalFocusChangeListener));
 
-        if (currentNotification.mIsNewHeadsUp) {
-            // Add swipe gesture
-            View cardView = currentNotification.getNotificationView().findViewById(R.id.card_view);
-            cardView.setOnTouchListener(new HeadsUpNotificationOnTouchListener(cardView,
-                    shouldDismissOnSwipe(alertEntry), () -> resetView(alertEntry)));
+        attachHunViewListeners(currentNotification.getNotificationView(), alertEntry);
+    }
 
-            // Add dismiss button listener
-            View dismissButton = currentNotification.getNotificationView().findViewById(
-                    R.id.dismiss_button);
-            if (dismissButton != null) {
-                dismissButton.setOnClickListener(v -> dismissHun(alertEntry));
-            }
+    private void attachHunViewListeners(View notificationView, AlertEntry alertEntry) {
+        // Add swipe gesture
+        View cardView = notificationView.findViewById(R.id.card_view);
+        cardView.setOnTouchListener(new HeadsUpNotificationOnTouchListener(cardView,
+                shouldDismissOnSwipe(alertEntry), () -> resetView(alertEntry)));
+
+        // Add dismiss button listener
+        View dismissButton = notificationView.findViewById(
+                R.id.dismiss_button);
+        if (dismissButton != null) {
+            dismissButton.setOnClickListener(v -> dismissHun(alertEntry));
         }
     }
 
diff --git a/src/com/android/car/notification/CarNotificationCenterActivity.java b/src/com/android/car/notification/CarNotificationCenterActivity.java
index 7cb1ff9..0e1c333 100644
--- a/src/com/android/car/notification/CarNotificationCenterActivity.java
+++ b/src/com/android/car/notification/CarNotificationCenterActivity.java
@@ -22,8 +22,13 @@
 import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 
+import com.android.internal.statusbar.IStatusBarService;
+
 /**
  * Displays all undismissed notifications.
  */
@@ -36,6 +41,9 @@
     private PreprocessingManager mPreprocessingManager;
     private CarNotificationView mCarNotificationView;
     private NotificationViewController mNotificationViewController;
+    private IStatusBarService mStatusBarService;
+    private NotificationDataManager mNotificationDataManager;
+    private CarNotificationVisibilityLogger mNotificationVisibilityLogger;
 
     private ServiceConnection mNotificationListenerConnectionListener = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder binder) {
@@ -49,6 +57,12 @@
                             app.getCarUxRestrictionWrapper());
             mNotificationViewController.enable();
             mNotificationListenerBound = true;
+            getApplicationContext().getMainExecutor().execute(() -> {
+                if (isResumed()) {
+                    notifyVisibilityChanged(/* isVisible= */ true);
+                    mCarNotificationView.setVisibleNotificationsAsSeen();
+                }
+            });
         }
 
         public void onServiceDisconnected(ComponentName className) {
@@ -69,6 +83,17 @@
         mCarNotificationView = findViewById(R.id.notification_view);
         mCarNotificationView.setClickHandlerFactory(app.getClickHandlerFactory());
         findViewById(R.id.exit_button_container).setOnClickListener(v -> finish());
+        mNotificationDataManager = NotificationDataManager.getInstance();
+        mStatusBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mNotificationVisibilityLogger = new CarNotificationVisibilityLogger(mStatusBarService,
+                mNotificationDataManager);
+
+        mNotificationDataManager.setOnUnseenCountUpdateListener(() -> {
+            mNotificationListener.setNotificationsShown(
+                    NotificationDataManager.getInstance().getSeenNotifications());
+            mNotificationVisibilityLogger.notifyVisibilityChanged(isResumed());
+        });
     }
 
     @Override
@@ -79,14 +104,15 @@
         intent.setAction(CarNotificationListener.ACTION_LOCAL_BINDING);
         bindService(intent, mNotificationListenerConnectionListener, Context.BIND_AUTO_CREATE);
         if (mNotificationViewController != null) {
-            mNotificationViewController.onVisibilityChanged(true);
+            mNotificationViewController.onVisibilityChanged(/* isVisible= */ true);
         }
     }
 
     @Override
     protected void onStop() {
         super.onStop();
-        mNotificationViewController.onVisibilityChanged(false);
+        notifyVisibilityChanged(/* isVisible= */ false);
+
         // Unbind notification listener
         if (mNotificationListenerBound) {
             mNotificationViewController.disable();
@@ -97,4 +123,17 @@
         }
     }
 
+    private void notifyVisibilityChanged(boolean isVisible) {
+        mNotificationViewController.onVisibilityChanged(isVisible);
+        try {
+            if (isVisible) {
+                mStatusBarService.onPanelRevealed(/* clearNotificationEffects= */ true,
+                        mNotificationDataManager.getVisibleNotifications().size());
+            } else {
+                mStatusBarService.onPanelHidden();
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Unable to report notification visibility changes", ex);
+        }
+    }
 }
diff --git a/src/com/android/car/notification/CarNotificationItemTouchListener.java b/src/com/android/car/notification/CarNotificationItemTouchListener.java
index d4bb2e6..908baaa 100644
--- a/src/com/android/car/notification/CarNotificationItemTouchListener.java
+++ b/src/com/android/car/notification/CarNotificationItemTouchListener.java
@@ -108,32 +108,34 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         mDismissAnimationHelper = new DismissAnimationHelper(context, (viewHolder) -> {
-            if (viewHolder.isDismissible()) {
-                AlertEntry notification = viewHolder.getAlertEntry();
-                // The grouped notification view holder returns a notification representing the
-                // group (SummaryNotification) when viewHolder.getAlertEntry() is
-                // called. The platform will clear all notifications sharing the group key
-                // attached to this notification. Since grouping is not strictly based on
-                // group key, it is preferred to dismiss notifications bound to the view holder
-                // individually.
-                if (viewHolder instanceof GroupNotificationViewHolder) {
-                    NotificationGroup notificationGroup =
-                            ((GroupNotificationViewHolder) viewHolder).getNotificationGroup();
-                    AlertEntry summaryNotification =
-                            notificationGroup.getGroupSummaryNotification();
-                    if (summaryNotification != null) {
-                        clearNotification(summaryNotification);
-                    }
-
-                    for (AlertEntry alertEntry
-                            : notificationGroup.getChildNotifications()) {
-                        clearNotification(alertEntry);
-                    }
-
-                } else {
-                    clearNotification(notification);
-                }
+            if (!viewHolder.isDismissible()) {
+                return;
             }
+            AlertEntry notification = viewHolder.getAlertEntry();
+            // The grouped notification view holder returns a notification representing the
+            // group (SummaryNotification) when viewHolder.getAlertEntry() is
+            // called. The platform will clear all notifications sharing the group key
+            // attached to this notification. Since grouping is not strictly based on
+            // group key, it is preferred to dismiss notifications bound to the view holder
+            // individually.
+            if (viewHolder instanceof GroupNotificationViewHolder) {
+                NotificationGroup notificationGroup =
+                        ((GroupNotificationViewHolder) viewHolder).getNotificationGroup();
+                AlertEntry summaryNotification =
+                        notificationGroup.getGroupSummaryNotification();
+                if (summaryNotification != null && mAdapter.shouldRemoveGroupSummary(
+                        notificationGroup.getGroupKey())) {
+                    clearNotification(summaryNotification);
+                    return;
+                }
+
+                for (AlertEntry alertEntry
+                        : notificationGroup.getChildNotifications()) {
+                    clearNotification(alertEntry);
+                }
+                return;
+            }
+            clearNotification(notification);
         });
 
         Resources res = context.getResources();
diff --git a/src/com/android/car/notification/CarNotificationTypeItem.java b/src/com/android/car/notification/CarNotificationTypeItem.java
index 9575647..044dc7a 100644
--- a/src/com/android/car/notification/CarNotificationTypeItem.java
+++ b/src/com/android/car/notification/CarNotificationTypeItem.java
@@ -65,9 +65,7 @@
             R.layout.basic_notification_template, NotificationViewType.BASIC, false),
     BASIC_IN_GROUP(-1, R.layout.basic_notification_template_inner,
             NotificationViewType.BASIC_IN_GROUP, true),
-    GROUP_EXPANDED(-1, R.layout.group_notification_template, NotificationViewType.GROUP_EXPANDED,
-            false),
-    GROUP_COLLAPSED(-1, R.layout.group_notification_template, NotificationViewType.GROUP_COLLAPSED,
+    GROUP(-1, R.layout.group_notification_template, NotificationViewType.GROUP,
             false),
     GROUP_SUMMARY(-1, R.layout.group_summary_notification_template,
             NotificationViewType.GROUP_SUMMARY, false);
@@ -137,8 +135,7 @@
             case NotificationViewType.INBOX:
             case NotificationViewType.INBOX_IN_GROUP:
                 return new InboxNotificationViewHolder(view, clickHandlerFactory);
-            case NotificationViewType.GROUP_EXPANDED:
-            case NotificationViewType.GROUP_COLLAPSED:
+            case NotificationViewType.GROUP:
                 return new GroupNotificationViewHolder(view, clickHandlerFactory);
             case NotificationViewType.GROUP_SUMMARY:
                 return new GroupSummaryNotificationViewHolder(view, clickHandlerFactory);
diff --git a/src/com/android/car/notification/CarNotificationView.java b/src/com/android/car/notification/CarNotificationView.java
index 954b9c4..fe5cfab 100644
--- a/src/com/android/car/notification/CarNotificationView.java
+++ b/src/com/android/car/notification/CarNotificationView.java
@@ -29,6 +29,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
 
+import com.android.car.notification.template.GroupNotificationViewHolder;
 import com.android.car.uxr.UxrContentLimiterImpl;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
@@ -207,6 +208,14 @@
      */
     public void resetState() {
         mAdapter.collapseAllGroups();
+        for (int i = 0; i < mAdapter.getItemCount(); i++) {
+            RecyclerView.ViewHolder holder = mListView.findViewHolderForAdapterPosition(i);
+            if (holder != null && holder.getItemViewType() == NotificationViewType.GROUP) {
+                GroupNotificationViewHolder groupNotificationViewHolder =
+                        (GroupNotificationViewHolder) holder;
+                groupNotificationViewHolder.collapseGroup();
+            }
+        }
     }
 
     @Override
@@ -415,7 +424,7 @@
         // No visible items are found.
         if (firstVisible == RecyclerView.NO_POSITION) return;
 
-        mAdapter.setNotificationsAsSeen(firstVisible, lastVisible);
+        mAdapter.setVisibleNotificationsAsSeen(firstVisible, lastVisible);
     }
 
     private void manageButtonOnClickListener(View v) {
@@ -433,4 +442,14 @@
         /** Allows handling of a {@link KeyEvent} if it isn't already handled by the superclass. */
         boolean dispatchKeyEvent(KeyEvent event);
     }
+
+    @VisibleForTesting
+    void setAdapter(CarNotificationViewAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    @VisibleForTesting
+    void setListView(RecyclerView listView) {
+        mListView = listView;
+    }
 }
diff --git a/src/com/android/car/notification/CarNotificationViewAdapter.java b/src/com/android/car/notification/CarNotificationViewAdapter.java
index a3344d8..57e650b 100644
--- a/src/com/android/car/notification/CarNotificationViewAdapter.java
+++ b/src/com/android/car/notification/CarNotificationViewAdapter.java
@@ -42,7 +42,9 @@
 import com.android.car.ui.recyclerview.ContentLimitingAdapter;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -68,6 +70,7 @@
     private final CarNotificationItemController mNotificationItemController;
 
     private List<NotificationGroup> mNotifications = new ArrayList<>();
+    private Map<String, Integer> mGroupKeyToCountMap = new HashMap<>();
     private LinearLayoutManager mLayoutManager;
     private RecyclerView.RecycledViewPool mViewPool;
     private CarUxRestrictions mCarUxRestrictions;
@@ -173,13 +176,11 @@
                 ((CarNotificationOlderViewHolder) holder)
                         .bind(mHasSeenNotifications, !mHasUnseenNotifications);
                 return;
-            case NotificationViewType.GROUP_EXPANDED:
+            case NotificationViewType.GROUP:
                 ((GroupNotificationViewHolder) holder)
-                        .bind(notificationGroup, this, /* isExpanded= */ true);
-                return;
-            case NotificationViewType.GROUP_COLLAPSED:
-                ((GroupNotificationViewHolder) holder)
-                        .bind(notificationGroup, this, /* isExpanded= */ false);
+                        .bind(notificationGroup, this, /* isExpanded= */
+                                isExpanded(notificationGroup.getGroupKey(),
+                                        notificationGroup.isSeen()));
                 return;
             case NotificationViewType.GROUP_SUMMARY:
                 ((CarNotificationBaseViewHolder) holder).setHideDismissButton(true);
@@ -222,11 +223,7 @@
                 new ExpandedNotification(notificationGroup.getGroupKey(),
                         notificationGroup.isSeen());
         if (notificationGroup.isGroup()) {
-            if (mExpandedNotifications.contains(expandedNotification)) {
-                return NotificationViewType.GROUP_EXPANDED;
-            } else {
-                return NotificationViewType.GROUP_COLLAPSED;
-            }
+            return NotificationViewType.GROUP;
         } else if (mExpandedNotifications.contains(expandedNotification)) {
             // when there are 2 notifications left in the expanded notification and one of them is
             // removed at that time the item type changes from group to normal and hence the
@@ -431,11 +428,19 @@
     /**
      * Updates notifications and update views.
      *
-     * @param setRecyclerViewListHeaderAndFooter sets the header and footer on the entire list of
+     * @param setRecyclerViewListHeadersAndFooters sets the header and footer on the entire list of
      * items within the recycler view. This is NOT the header/footer for the grouped notifications.
      */
     public void setNotifications(List<NotificationGroup> notifications,
             boolean setRecyclerViewListHeadersAndFooters) {
+        mGroupKeyToCountMap.clear();
+        notifications.forEach(notificationGroup -> {
+            if ((mGroupKeyToCountMap.computeIfPresent(notificationGroup.getGroupKey(),
+                    (key, currentValue) -> currentValue + 1)) == null) {
+                mGroupKeyToCountMap.put(notificationGroup.getGroupKey(), 1);
+            }
+        });
+
         if (mShowRecentsAndOlderHeaders && !mIsGroupNotificationAdapter) {
             List<NotificationGroup> seenNotifications = new ArrayList<>();
             List<NotificationGroup> unseenNotifications = new ArrayList<>();
@@ -639,6 +644,13 @@
     }
 
     /**
+     * Returns {@code true} if there are multiple groups with the same {@code groupKey}.
+     */
+    public boolean shouldRemoveGroupSummary(String groupKey) {
+        return mGroupKeyToCountMap.getOrDefault(groupKey, /* defaultValue= */ 0) <= 1;
+    }
+
+    /**
      * Sets the NotificationClickHandlerFactory that allows for a hook to run a block off code
      * when  the notification is clicked. This is useful to dismiss a screen after
      * a notification list clicked.
@@ -653,7 +665,7 @@
      * @param start Initial adapter position of the notification groups.
      * @param end Final adapter position of the notification groups.
      */
-    /* package */ void setNotificationsAsSeen(int start, int end) {
+    void setVisibleNotificationsAsSeen(int start, int end) {
         if (mNotificationDataManager == null || mIsGroupNotificationAdapter) {
             return;
         }
@@ -664,7 +676,7 @@
         List<AlertEntry> notifications = new ArrayList();
         for (int i = start; i <= end; i++) {
             NotificationGroup group = mNotifications.get(i);
-            AlertEntry groupSummary =  group.getGroupSummaryNotification();
+            AlertEntry groupSummary = group.getGroupSummaryNotification();
             if (groupSummary != null) {
                 notifications.add(groupSummary);
             }
@@ -672,7 +684,7 @@
             notifications.addAll(group.getChildNotifications());
         }
 
-        mNotificationDataManager.setNotificationsAsSeen(notifications);
+        mNotificationDataManager.setVisibleNotificationsAsSeen(notifications);
     }
 
     @Override
@@ -691,7 +703,7 @@
 
         @Override
         public boolean equals(Object obj) {
-            if (!(obj instanceof  ExpandedNotification)) {
+            if (!(obj instanceof ExpandedNotification)) {
                 return false;
             }
             ExpandedNotification other = (ExpandedNotification) obj;
diff --git a/src/com/android/car/notification/CarNotificationVisibilityLogger.java b/src/com/android/car/notification/CarNotificationVisibilityLogger.java
new file mode 100644
index 0000000..8fd1f9f
--- /dev/null
+++ b/src/com/android/car/notification/CarNotificationVisibilityLogger.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.notification;
+
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Handles notification logging, in particular, logging which notifications are visible and which
+ * are not.
+ */
+public class CarNotificationVisibilityLogger {
+
+    private static final String TAG = "CarNotifVisLogger";
+
+    private final ArraySet<NotificationVisibility> mCurrentlyVisible = new ArraySet<>();
+
+    private final NotificationDataManager mNotificationDataManager;
+    private IStatusBarService mStatusBarService;
+
+    CarNotificationVisibilityLogger(IStatusBarService statusBarService,
+            NotificationDataManager notificationDataManager) {
+        mStatusBarService = statusBarService;
+        mNotificationDataManager = notificationDataManager;
+    }
+
+    private NotificationVisibility obtainNotificationVisibility(AlertEntry entry, int count) {
+        return NotificationVisibility.obtain(
+                entry.getKey(),
+                /* rank= */ -1,
+                count,
+                /* isVisible= */ true,
+                NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA);
+    }
+
+    /**
+     * Notifies the appropriate services that the notification state might have changed.
+     *
+     * @param isVisible Whether the notification panel is visible or not. If it is false the
+     *                  method assumes that all the notifications are invisible.
+     */
+    public void notifyVisibilityChanged(boolean isVisible) {
+        ArraySet<NotificationVisibility> previouslyVisible = new ArraySet<>(mCurrentlyVisible);
+
+        ArraySet<NotificationVisibility> newlyVisible = new ArraySet<>();
+        ArraySet<NotificationVisibility> noLongerVisible = new ArraySet<>();
+
+        mCurrentlyVisible.clear();
+
+        if (isVisible) {
+            List<AlertEntry> entries = mNotificationDataManager.getVisibleNotifications();
+            int count = entries.size();
+
+            mCurrentlyVisible.addAll(
+                    entries.stream().map(entry -> obtainNotificationVisibility(entry, count))
+                            .collect(Collectors.toSet()));
+
+            newlyVisible.addAll(mCurrentlyVisible);
+            newlyVisible.removeAll(previouslyVisible);
+
+            noLongerVisible.addAll(previouslyVisible);
+            noLongerVisible.removeAll(mCurrentlyVisible);
+        } else {
+            noLongerVisible.addAll(previouslyVisible);
+        }
+
+        onNotificationVisibilityChanged(newlyVisible, previouslyVisible);
+        recycleAndClear(noLongerVisible);
+    }
+
+    /**
+     * Notify StatusBarService of change in notifications' visibility.
+     *
+     * @param newlyVisible Notifications that became visible.
+     * @param noLongerVisible Notifications that are no longer visible.
+     */
+    private void onNotificationVisibilityChanged(
+            Set<NotificationVisibility> newlyVisible, Set<NotificationVisibility> noLongerVisible) {
+        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+            return;
+        }
+
+        try {
+            NotificationVisibility[] newlyVisibleArray = createDeepClone(newlyVisible)
+                    .toArray(new NotificationVisibility[0]);
+
+            NotificationVisibility[] noLongerVisibleArray = createDeepClone(noLongerVisible)
+                    .toArray(new NotificationVisibility[0]);
+
+            mStatusBarService.onNotificationVisibilityChanged(newlyVisibleArray,
+                    noLongerVisibleArray);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to notify StatusBarService of notification visibility change");
+        }
+    }
+
+    /**
+     * Clears array and recycles NotificationVisibility objects for reuse.
+     *
+     * @param set The array that needs to be cleared.
+     */
+    private static void recycleAndClear(Set<NotificationVisibility> set) {
+        set.stream().forEach(NotificationVisibility::recycle);
+        set.clear();
+    }
+
+    /**
+     * Creates a deep clone of the collection by cloning each item of the collection.
+     *
+     * @param set The collection that has to be cloned.
+     */
+    private static Set<NotificationVisibility> createDeepClone(
+            Set<NotificationVisibility> set) {
+        return set.stream().map(NotificationVisibility::clone).collect(Collectors.toSet());
+    }
+}
diff --git a/src/com/android/car/notification/NotificationDataManager.java b/src/com/android/car/notification/NotificationDataManager.java
index 72ed5a6..2b54e66 100644
--- a/src/com/android/car/notification/NotificationDataManager.java
+++ b/src/com/android/car/notification/NotificationDataManager.java
@@ -115,12 +115,7 @@
                 mUnseenNotificationMap.put(alertEntry.getKey(), true);
                 mVisibleNotifications.add(alertEntry);
 
-                if (mOnUnseenCountUpdateListener != null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Unseen notification map: " + mUnseenNotificationMap);
-                    }
-                    mOnUnseenCountUpdateListener.onUnseenCountUpdate();
-                }
+                notifyUnseenCountUpdateListeners();
             }
         }
     }
@@ -128,12 +123,7 @@
     void untrackUnseenNotification(AlertEntry alertEntry) {
         if (mUnseenNotificationMap.containsKey(alertEntry.getKey())) {
             mUnseenNotificationMap.remove(alertEntry.getKey());
-            if (mOnUnseenCountUpdateListener != null) {
-                if (DEBUG) {
-                    Log.d(TAG, "UnseenNotificationMap: " + mUnseenNotificationMap);
-                }
-                mOnUnseenCountUpdateListener.onUnseenCountUpdate();
-            }
+            notifyUnseenCountUpdateListeners();
         }
     }
 
@@ -169,12 +159,7 @@
             mUnseenNotificationMap.remove(notificationKey);
         }
 
-        if (mOnUnseenCountUpdateListener != null) {
-            if (DEBUG) {
-                Log.d(TAG, "UnseenNotificationMap: " + mUnseenNotificationMap);
-            }
-            mOnUnseenCountUpdateListener.onUnseenCountUpdate();
-        }
+        notifyUnseenCountUpdateListeners();
     }
 
     boolean isNotificationSeen(AlertEntry alertEntry) {
@@ -219,15 +204,15 @@
         mUnseenNotificationMap.clear();
         mVisibleNotifications.clear();
 
-        if (mOnUnseenCountUpdateListener != null) {
-            if (DEBUG) {
-                Log.d(TAG, "Unseen notifications cleared");
-            }
-            mOnUnseenCountUpdateListener.onUnseenCountUpdate();
-        }
+        notifyUnseenCountUpdateListeners();
     }
 
-    void setNotificationsAsSeen(List<AlertEntry> alertEntries) {
+    /**
+     * Uses the {@code alertEntries} to reset the visible notifications and marks them as seen.
+     *
+     * @param alertEntries List of {@link AlertEntry} that are currently visible to be marked seen.
+     */
+    void setVisibleNotificationsAsSeen(List<AlertEntry> alertEntries) {
         mVisibleNotifications.clear();
         for (AlertEntry alertEntry : alertEntries) {
             if (mUnseenNotificationMap.containsKey(alertEntry.getKey())) {
@@ -235,12 +220,15 @@
                 mVisibleNotifications.add(alertEntry);
             }
         }
-        if (mOnUnseenCountUpdateListener != null) {
-            if (DEBUG) {
-                Log.d(TAG, "Unseen notification map: " + mUnseenNotificationMap);
-            }
-            mOnUnseenCountUpdateListener.onUnseenCountUpdate();
-        }
+        notifyUnseenCountUpdateListeners();
+    }
+
+    /**
+     * @param alertEntry {@link AlertEntry} to be marked seen and notify listeners.
+     */
+    void setNotificationAsSeen(AlertEntry alertEntry) {
+        mUnseenNotificationMap.put(alertEntry.getKey(), false);
+        notifyUnseenCountUpdateListeners();
     }
 
     /**
@@ -283,4 +271,14 @@
                 .map(map -> map.getKey())
                 .toArray(String[]::new);
     }
+
+    private void notifyUnseenCountUpdateListeners() {
+        if (mOnUnseenCountUpdateListener == null) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Unseen notifications cleared");
+        }
+        mOnUnseenCountUpdateListener.onUnseenCountUpdate();
+    }
 }
diff --git a/src/com/android/car/notification/NotificationUtils.java b/src/com/android/car/notification/NotificationUtils.java
index 9546724..8c3c8b0 100644
--- a/src/com/android/car/notification/NotificationUtils.java
+++ b/src/com/android/car/notification/NotificationUtils.java
@@ -28,8 +28,6 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
-import androidx.annotation.VisibleForTesting;
-
 import com.android.internal.graphics.ColorUtils;
 
 public class NotificationUtils {
@@ -41,15 +39,6 @@
     private static final float MAX_LIGHTNESS = 1;
     private static final float LIGHT_COLOR_LUMINANCE_THRESHOLD = 0.5f;
 
-    /**
-     * Key that system apps can add to the Notification extras to override the default
-     * {@link R.bool.config_useLauncherIcon} behavior. If this is set to false, a small and a large
-     * icon should be specified to be shown properly in the relevant default configuration.
-     */
-    @VisibleForTesting
-    static final String EXTRA_USE_LAUNCHER_ICON =
-            "com.android.car.notification.EXTRA_USE_LAUNCHER_ICON";
-
     private NotificationUtils() {
     }
 
@@ -167,22 +156,6 @@
         return Color.luminance(backgroundColor) > LIGHT_COLOR_LUMINANCE_THRESHOLD;
     }
 
-    /**
-     * Returns true if the launcher icon should be used for a given notification.
-     */
-    public static boolean shouldUseLauncherIcon(Context context, StatusBarNotification sbn) {
-        Bundle notificationExtras = sbn.getNotification().extras;
-        if (notificationExtras == null) {
-            return context.getResources().getBoolean(R.bool.config_useLauncherIcon);
-        }
-
-        if (notificationExtras.containsKey(EXTRA_USE_LAUNCHER_ICON)
-                && isSystemApp(context, sbn)) {
-            return notificationExtras.getBoolean(EXTRA_USE_LAUNCHER_ICON);
-        }
-        return context.getResources().getBoolean(R.bool.config_useLauncherIcon);
-    }
-
     private static boolean isSystemPrivilegedOrPlatformKeyInner(Context context,
             AlertEntry alertEntry, boolean checkForPrivilegedApp) {
         PackageInfo packageInfo = getPackageInfo(context, alertEntry.getStatusBarNotification());
diff --git a/src/com/android/car/notification/NotificationViewType.java b/src/com/android/car/notification/NotificationViewType.java
index 52b0eea..5e99eff 100644
--- a/src/com/android/car/notification/NotificationViewType.java
+++ b/src/com/android/car/notification/NotificationViewType.java
@@ -21,8 +21,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 @IntDef({
-        NotificationViewType.GROUP_COLLAPSED,
-        NotificationViewType.GROUP_EXPANDED,
+        NotificationViewType.GROUP,
         NotificationViewType.GROUP_SUMMARY,
         NotificationViewType.BASIC,
         NotificationViewType.BASIC_IN_GROUP,
@@ -46,35 +45,34 @@
 @Retention(RetentionPolicy.SOURCE)
 @interface NotificationViewType {
 
-    int GROUP_COLLAPSED = 1;
-    int GROUP_EXPANDED = 2;
-    int GROUP_SUMMARY = 3;
+    int GROUP = 1;
+    int GROUP_SUMMARY = 2;
 
-    int BASIC = 4;
-    int BASIC_IN_GROUP = 5;
+    int BASIC = 3;
+    int BASIC_IN_GROUP = 4;
 
-    int MESSAGE = 6;
-    int MESSAGE_IN_GROUP = 7;
+    int MESSAGE = 5;
+    int MESSAGE_IN_GROUP = 6;
 
-    int PROGRESS = 8;
-    int PROGRESS_IN_GROUP = 9;
+    int PROGRESS = 7;
+    int PROGRESS_IN_GROUP = 8;
 
-    int INBOX = 10;
-    int INBOX_IN_GROUP = 11;
+    int INBOX = 9;
+    int INBOX_IN_GROUP = 10;
 
-    int CAR_EMERGENCY = 12;
+    int CAR_EMERGENCY = 11;
 
-    int CAR_WARNING = 13;
+    int CAR_WARNING = 12;
 
-    int CAR_INFORMATION = 14;
-    int CAR_INFORMATION_IN_GROUP = 15;
+    int CAR_INFORMATION = 13;
+    int CAR_INFORMATION_IN_GROUP = 14;
 
-    int NAVIGATION = 16;
-    int CALL = 17;
+    int NAVIGATION = 15;
+    int CALL = 16;
 
-    int HEADER = 18;
-    int FOOTER = 19;
+    int HEADER = 17;
+    int FOOTER = 18;
 
-    int RECENTS = 20;
-    int OLDER = 21;
+    int RECENTS = 19;
+    int OLDER = 20;
 }
\ No newline at end of file
diff --git a/src/com/android/car/notification/PreprocessingManager.java b/src/com/android/car/notification/PreprocessingManager.java
index 9a58ccd..2ee29ce 100644
--- a/src/com/android/car/notification/PreprocessingManager.java
+++ b/src/com/android/car/notification/PreprocessingManager.java
@@ -39,8 +39,10 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.UUID;
@@ -151,9 +153,9 @@
             Map<String, AlertEntry> notifications, RankingMap rankingMap) {
         return new ArrayList<>(
                 rank(group(optimizeForDriving(
-                        filter(showLessImportantNotifications,
-                                new ArrayList<>(notifications.values()),
-                                rankingMap))),
+                                filter(showLessImportantNotifications,
+                                        new ArrayList<>(notifications.values()),
+                                        rankingMap))),
                         rankingMap));
     }
 
@@ -310,7 +312,7 @@
      * for the presentation-level text truncation.
      */
     AlertEntry optimizeForDriving(AlertEntry alertEntry) {
-        if (Notification.CATEGORY_MESSAGE.equals(alertEntry.getNotification().category)){
+        if (Notification.CATEGORY_MESSAGE.equals(alertEntry.getNotification().category)) {
             return alertEntry;
         }
 
@@ -561,11 +563,15 @@
             RankingMap newRankingMap, boolean isUpdate) {
         Notification notification = newNotification.getNotification();
         NotificationGroup newGroup = new NotificationGroup();
+
+        // The newGroup should appear in the recent section and marked as seen
+        // because the panel is open.
         newGroup.setSeen(false);
+        mNotificationDataManager.setNotificationAsSeen(newNotification);
 
         if (notification.isGroupSummary()) {
             // If child notifications already exist, update group summary
-            for (NotificationGroup oldGroup: mOldProcessedNotifications) {
+            for (NotificationGroup oldGroup : mOldProcessedNotifications) {
                 if (hasSameGroupKey(oldGroup.getSingleNotification(), newNotification)) {
                     oldGroup.setGroupSummaryNotification(newNotification);
                     return mOldProcessedNotifications;
@@ -576,47 +582,99 @@
             insertRankedNotification(newGroup, newRankingMap);
             return mOldProcessedNotifications;
         } else {
-            newGroup.addNotification(newNotification);
+            // To keep track of indexes of unseen Notifications with the same group key
+            Set<Integer> indexOfUnseenGroupsWithSameGroupKey = new HashSet<>();
             for (int i = 0; i < mOldProcessedNotifications.size(); i++) {
                 NotificationGroup oldGroup = mOldProcessedNotifications.get(i);
+
+                // If an unseen group already exists with the same group key
                 if (TextUtils.equals(oldGroup.getGroupKey(),
                         newNotification.getStatusBarNotification().getGroupKey())
                         && (!mShowRecentsAndOlderHeaders || !oldGroup.isSeen())) {
-                    // If an unseen group already exists
+                    indexOfUnseenGroupsWithSameGroupKey.add(i);
+
+                    // If a group already exist with no children
                     if (oldGroup.getChildCount() == 0) {
-                        // If a standalone group summary exists
+                        // A group with no children is a standalone group summary
+                        NotificationGroup group = oldGroup;
                         if (isUpdate) {
-                            // This is an update; replace the group summary notification
-                            mOldProcessedNotifications.set(i, newGroup);
-                        } else {
-                            // Adding new notification; add to existing group
-                            oldGroup.addNotification(newNotification);
-                            mOldProcessedNotifications.set(i, oldGroup);
+                            // Replace the standalone group summary
+                            group = newGroup;
                         }
+                        group.addNotification(newNotification);
+                        mOldProcessedNotifications.set(i, group);
                         return mOldProcessedNotifications;
                     }
-                    // If a group already exist with multiple children, insert outside of the group
+
+                    // Group with same group key exist with multiple children
+                    // For update, replace the old notification with the updated notification
+                    // else add the new notification to the existing group if it's notification
+                    // count is greater than the minimum threshold.
                     if (isUpdate) {
                         oldGroup.removeNotification(newNotification);
                     }
-                    oldGroup.addNotification(newNotification);
-                    mOldProcessedNotifications.set(i, oldGroup);
-                    return mOldProcessedNotifications;
+                    if (isUpdate || oldGroup.getChildCount() >= mMinimumGroupingThreshold) {
+                        oldGroup.addNotification(newNotification);
+                        mOldProcessedNotifications.set(i, oldGroup);
+                        return mOldProcessedNotifications;
+                    }
                 }
             }
-            // If it is a new notification, insert directly
+
+            // Not an update to an existing group and no groups with same group key and
+            // child count > minimum grouping threshold or child count == 0 exist in the list.
+            AlertEntry groupSummaryNotification = findGroupSummaryNotification(
+                    newNotification.getStatusBarNotification().getGroupKey());
+            // If the number of unseen notifications (+1 to account for new notification being
+            // added) with same group key is greater than the minimum grouping threshold
+            if (((indexOfUnseenGroupsWithSameGroupKey.size() + 1) >= mMinimumGroupingThreshold)
+                    && groupSummaryNotification != null) {
+                // Remove all individual groups and add all notifications with the same group key
+                // to the new group
+                List<NotificationGroup> otherProcessedNotifications = new ArrayList<>();
+                for (int i = 0; i < mOldProcessedNotifications.size(); i++) {
+                    NotificationGroup notificationGroup = mOldProcessedNotifications.get(i);
+                    if (indexOfUnseenGroupsWithSameGroupKey.contains(i)) {
+                        // Group has the same group key
+                        for (AlertEntry alertEntry : notificationGroup.getChildNotifications()) {
+                            newGroup.addNotification(alertEntry);
+                        }
+                    } else {
+                        otherProcessedNotifications.add(notificationGroup);
+                    }
+                }
+                mOldProcessedNotifications = otherProcessedNotifications;
+                mNotificationDataManager.setNotificationAsSeen(groupSummaryNotification);
+                newGroup.setGroupSummaryNotification(groupSummaryNotification);
+            }
+
+            newGroup.addNotification(newNotification);
             insertRankedNotification(newGroup, newRankingMap);
             return mOldProcessedNotifications;
         }
     }
 
+    /**
+     * Finds Group Summary Notification with the same group key from {@code mOldNotifications}.
+     */
+    @Nullable
+    private AlertEntry findGroupSummaryNotification(String groupKey) {
+        for (AlertEntry alertEntry : mOldNotifications.values()) {
+            if (alertEntry.getNotification().isGroupSummary() && TextUtils.equals(
+                    alertEntry.getStatusBarNotification().getGroupKey(), groupKey)) {
+                return alertEntry;
+            }
+        }
+        return null;
+    }
+
     // When adding a new notification we want to add it before the next highest ranked without
     // changing existing order
     private void insertRankedNotification(NotificationGroup group, RankingMap newRankingMap) {
         NotificationListenerService.Ranking newRanking = new NotificationListenerService.Ranking();
         newRankingMap.getRanking(group.getNotificationForSorting().getKey(), newRanking);
 
-        for(int i = 0; i < mOldProcessedNotifications.size(); i++) {
+        for (int i = 0; i < mOldProcessedNotifications.size(); i++) {
             NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
             newRankingMap.getRanking(mOldProcessedNotifications.get(
                     i).getNotificationForSorting().getKey(), ranking);
@@ -626,7 +684,7 @@
                 return;
             }
 
-            if(newRanking.getRank() < ranking.getRank()) {
+            if (newRanking.getRank() < ranking.getRank()) {
                 mOldProcessedNotifications.add(i, group);
                 return;
             }
diff --git a/src/com/android/car/notification/template/BasicNotificationViewHolder.java b/src/com/android/car/notification/template/BasicNotificationViewHolder.java
index 9343a44..9ab418b 100644
--- a/src/com/android/car/notification/template/BasicNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/BasicNotificationViewHolder.java
@@ -21,7 +21,6 @@
 
 import com.android.car.notification.AlertEntry;
 import com.android.car.notification.NotificationClickHandlerFactory;
-import com.android.car.notification.NotificationUtils;
 import com.android.car.notification.R;
 
 /**
@@ -62,10 +61,8 @@
         Bundle extraData = notification.extras;
         CharSequence title = extraData.getCharSequence(Notification.EXTRA_TITLE);
         CharSequence text = extraData.getCharSequence(Notification.EXTRA_TEXT);
-        boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(),
-                alertEntry.getStatusBarNotification());
-        mBodyView.bind(title, text, useLauncherIcon,
-                loadAppLauncherIcon(alertEntry.getStatusBarNotification()),
+        mBodyView.bind(title, text,
+                alertEntry.getStatusBarNotification(),
                 notification.getLargeIcon(), /* titleIcon= */ null, /* countText= */ null,
                 notification.showsTime() ? notification.when : null);
     }
diff --git a/src/com/android/car/notification/template/CallNotificationViewHolder.java b/src/com/android/car/notification/template/CallNotificationViewHolder.java
index 53365e7..478d8bf 100644
--- a/src/com/android/car/notification/template/CallNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/CallNotificationViewHolder.java
@@ -21,7 +21,6 @@
 
 import com.android.car.notification.AlertEntry;
 import com.android.car.notification.NotificationClickHandlerFactory;
-import com.android.car.notification.NotificationUtils;
 import com.android.car.notification.R;
 
 /**
@@ -62,10 +61,8 @@
         Bundle extraData = notification.extras;
         CharSequence title = extraData.getCharSequence(Notification.EXTRA_TITLE);
         CharSequence text = extraData.getCharSequence(Notification.EXTRA_TEXT);
-        boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(),
-                alertEntry.getStatusBarNotification());
-        mBodyView.bind(title, text, useLauncherIcon,
-                loadAppLauncherIcon(alertEntry.getStatusBarNotification()),
+        mBodyView.bind(title, text,
+                alertEntry.getStatusBarNotification(),
                 notification.getLargeIcon(), /* titleIcon= */ null, /* countText= */ null,
                 notification.showsTime() ? notification.when : null);
     }
diff --git a/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java b/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java
index f335a6e..77c049b 100644
--- a/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java
+++ b/src/com/android/car/notification/template/CarNotificationBaseViewHolder.java
@@ -21,9 +21,6 @@
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.ImageButton;
@@ -167,7 +164,7 @@
         bindBody(mBodyView, isInGroup);
     }
 
-    Context getContext() {
+    protected final Context getContext() {
         return mContext;
     }
 
@@ -407,14 +404,4 @@
     View.OnClickListener getDismissHandler(AlertEntry alertEntry) {
         return mClickHandlerFactory.getDismissHandler(alertEntry);
     }
-
-    @Nullable
-    Drawable loadAppLauncherIcon(StatusBarNotification sbn) {
-        if (!NotificationUtils.shouldUseLauncherIcon(mContext, sbn)) {
-            return null;
-        }
-        Context packageContext = sbn.getPackageContext(mContext);
-        PackageManager pm = packageContext.getPackageManager();
-        return pm.getApplicationIcon(packageContext.getApplicationInfo());
-    }
 }
diff --git a/src/com/android/car/notification/template/CarNotificationBodyView.java b/src/com/android/car/notification/template/CarNotificationBodyView.java
index 29ac116..ef98b5f 100644
--- a/src/com/android/car/notification/template/CarNotificationBodyView.java
+++ b/src/com/android/car/notification/template/CarNotificationBodyView.java
@@ -19,11 +19,14 @@
 import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -54,6 +57,15 @@
     private final int mDefaultSecondaryTextColor;
     private final boolean mDefaultUseLauncherIcon;
 
+    /**
+     * Key that system apps can add to the Notification extras to override the default
+     * {@link R.bool.config_useLauncherIcon} behavior. If this is set to false, a small and a large
+     * icon should be specified to be shown properly in the relevant default configuration.
+     */
+    @VisibleForTesting
+    static final String EXTRA_USE_LAUNCHER_ICON =
+            "com.android.car.notification.EXTRA_USE_LAUNCHER_ICON";
+
     private boolean mIsHeadsUp;
     private boolean mShowBigIcon;
     private int mMaxLines;
@@ -139,11 +151,13 @@
      * @param countText text signifying the number of messages inside this notification
      * @param when      wall clock time in milliseconds for the notification
      */
-    public void bind(CharSequence title, @Nullable CharSequence content, boolean useLauncherIcon,
-            @Nullable Drawable launcherIcon, @Nullable Icon largeIcon, @Nullable Drawable titleIcon,
+    public void bind(CharSequence title, @Nullable CharSequence content,
+            StatusBarNotification sbn, @Nullable Icon largeIcon, @Nullable Drawable titleIcon,
             @Nullable CharSequence countText, @Nullable Long when) {
         setVisibility(View.VISIBLE);
 
+        boolean useLauncherIcon = setUseLauncherIcon(sbn);
+        Drawable launcherIcon = loadAppLauncherIcon(sbn);
         if (mLargeIconView != null) {
             if (useLauncherIcon && launcherIcon != null) {
                 mLargeIconView.setVisibility(View.VISIBLE);
@@ -221,7 +235,6 @@
         }
     }
 
-
     /**
      * Sets the secondary text color.
      */
@@ -291,6 +304,32 @@
         }
     }
 
+    /**
+     * Returns true if the launcher icon should be used for a given notification.
+     */
+    private boolean setUseLauncherIcon(StatusBarNotification sbn) {
+        Bundle notificationExtras = sbn.getNotification().extras;
+        if (notificationExtras == null) {
+            return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon);
+        }
+
+        if (notificationExtras.containsKey(EXTRA_USE_LAUNCHER_ICON)
+                && NotificationUtils.isSystemApp(getContext(), sbn)) {
+            return notificationExtras.getBoolean(EXTRA_USE_LAUNCHER_ICON);
+        }
+        return getContext().getResources().getBoolean(R.bool.config_useLauncherIcon);
+    }
+
+    @Nullable
+    private Drawable loadAppLauncherIcon(StatusBarNotification sbn) {
+        if (!setUseLauncherIcon(sbn)) {
+            return null;
+        }
+        Context packageContext = sbn.getPackageContext(getContext());
+        PackageManager pm = packageContext.getPackageManager();
+        return pm.getApplicationIcon(packageContext.getApplicationInfo());
+    }
+
     @VisibleForTesting
     TextView getTitleView() {
         return mTitleView;
diff --git a/src/com/android/car/notification/template/EmergencyNotificationViewHolder.java b/src/com/android/car/notification/template/EmergencyNotificationViewHolder.java
index bee10d0..b313b8a 100644
--- a/src/com/android/car/notification/template/EmergencyNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/EmergencyNotificationViewHolder.java
@@ -24,7 +24,6 @@
 
 import com.android.car.notification.AlertEntry;
 import com.android.car.notification.NotificationClickHandlerFactory;
-import com.android.car.notification.NotificationUtils;
 import com.android.car.notification.R;
 
 /**
@@ -73,11 +72,9 @@
         Bundle extraData = notification.extras;
         CharSequence title = extraData.getCharSequence(Notification.EXTRA_TITLE);
         CharSequence text = extraData.getCharSequence(Notification.EXTRA_TEXT);
-        boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(),
-                alertEntry.getStatusBarNotification());
 
-        mBodyView.bind(title, text, useLauncherIcon,
-                loadAppLauncherIcon(alertEntry.getStatusBarNotification()),
+        mBodyView.bind(title, text,
+                alertEntry.getStatusBarNotification(),
                 notification.getLargeIcon(), /* titleIcon= */ null, /* countText= */ null,
                 notification.showsTime() ? notification.when : null);
     }
diff --git a/src/com/android/car/notification/template/GroupNotificationViewHolder.java b/src/com/android/car/notification/template/GroupNotificationViewHolder.java
index c9e3712..2818d6c 100644
--- a/src/com/android/car/notification/template/GroupNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/GroupNotificationViewHolder.java
@@ -45,8 +45,10 @@
 import com.android.car.notification.NotificationClickHandlerFactory;
 import com.android.car.notification.NotificationGroup;
 import com.android.car.notification.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -64,7 +66,6 @@
     private final TextView mExpansionFooterView;
     private final View mExpansionFooterGroup;
     private final RecyclerView mNotificationListView;
-    private final CarNotificationViewAdapter mAdapter;
     private final Drawable mExpandDrawable;
     private final Drawable mCollapseDrawable;
     private final Paint mPaint;
@@ -75,6 +76,8 @@
     private final int mExpandedGroupNotificationIncrementSize;
     private final String mShowLessText;
 
+    private CarNotificationViewAdapter mAdapter;
+    private CarNotificationViewAdapter mParentAdapter;
     private AlertEntry mSummaryNotification;
     private NotificationGroup mNotificationGroup;
     private String mHeaderName;
@@ -144,7 +147,12 @@
                 .getInteger(R.integer.config_expandedGroupNotificationIncrementSize);
         mShowLessText = getContext().getString(R.string.collapse_group);
 
-        mNotificationListView.setLayoutManager(new LinearLayoutManager(getContext()));
+        mNotificationListView.setLayoutManager(new LinearLayoutManager(getContext()) {
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return false;
+            }
+        });
         mNotificationListView.addItemDecoration(new GroupedNotificationItemDecoration());
         ((SimpleItemAnimator) mNotificationListView.getItemAnimator())
                 .setSupportsChangeAnimations(false);
@@ -186,6 +194,7 @@
         reset();
 
         mNotificationGroup = group;
+        mParentAdapter = parentAdapter;
         mSummaryNotification = mNotificationGroup.getGroupSummaryNotification();
         mHeaderName = loadHeaderAppName(mSummaryNotification.getStatusBarNotification());
         mExpandedGroupHeaderTextView.setText(mHeaderName);
@@ -198,56 +207,12 @@
         // use the same view pool with all the grouped notifications
         // to increase the number of the shared views and reduce memory cost
         // the view pool is created and stored in the root adapter
-        mNotificationListView.setRecycledViewPool(parentAdapter.getViewPool());
+        mNotificationListView.setRecycledViewPool(mParentAdapter.getViewPool());
 
         // notification cards
         if (isExpanded) {
-            mNumberOfShownNotifications = 0;
-            // show header divider
-            mHeaderDividerView.setVisibility(View.VISIBLE);
-
-            mNotificationGroupsShown = new ArrayList<>();
-            mNumberOfShownNotifications =
-                    addNextPageOfNotificationsToList(mNotificationGroupsShown);
-
-            if (mUseLauncherIcon) {
-                mExpandedGroupHeader.setVisibility(View.VISIBLE);
-            } else {
-                mExpandedGroupHeader.setVisibility(View.GONE);
-            }
-        } else {
-            mExpandedGroupHeader.setVisibility(View.GONE);
-            // hide header divider
-            mHeaderDividerView.setVisibility(View.GONE);
-
-            NotificationGroup newGroup = new NotificationGroup();
-            newGroup.setSeen(mNotificationGroup.isSeen());
-
-            if (mUseLauncherIcon) {
-                // Only show first notification since notification header is not being used.
-                newGroup.addNotification(mNotificationGroup.getChildNotifications().get(0));
-                mNumberOfShownNotifications = 1;
-            } else {
-                // Only show group summary notification
-                newGroup.addNotification(mNotificationGroup.getGroupSummaryNotification());
-                // If the group summary notification is automatically generated,
-                // it does not contain a summary of the titles of the child notifications.
-                // Therefore, we generate a list of the child notification titles from
-                // the parent notification group, and pass them on.
-                newGroup.setChildTitles(mNotificationGroup.generateChildTitles());
-                mNumberOfShownNotifications = 0;
-            }
-
-            List<NotificationGroup> list = new ArrayList<>();
-            list.add(newGroup);
-            mNotificationGroupsShown = list;
-        }
-        mAdapter.setNotifications(mNotificationGroupsShown,
-                /* setRecyclerViewListHeadersAndFooters= */ false);
-
-        updateExpansionIcon(isExpanded);
-        updateOnClickListener(parentAdapter, isExpanded);
-        if (isExpanded) {
+            expandGroup();
+            addNotifications();
             if (mUseLauncherIcon) {
                 if (!itemView.isInTouchMode()) {
                     mCurrentFocusRequestState = FocusRequestStates.EXPANDED_GROUP_HEADER;
@@ -256,6 +221,7 @@
                 }
             }
         } else {
+            collapseGroup();
             if (mUseLauncherIcon) {
                 if (!itemView.isInTouchMode()) {
                     mCurrentFocusRequestState = FocusRequestStates.CARD_VIEW;
@@ -266,6 +232,66 @@
         }
     }
 
+    /**
+     * Expands the {@link GroupNotificationViewHolder}.
+     */
+    private void expandGroup() {
+        mNumberOfShownNotifications = 0;
+        mHeaderDividerView.setVisibility(View.VISIBLE);
+        mNotificationGroupsShown = new ArrayList<>();
+        if (mUseLauncherIcon) {
+            mExpandedGroupHeader.setVisibility(View.VISIBLE);
+        } else {
+            mExpandedGroupHeader.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Adds notifications to {@link GroupNotificationViewHolder}.
+     */
+    private void addNotifications() {
+        mNumberOfShownNotifications =
+                addNextPageOfNotificationsToList(mNotificationGroupsShown);
+        mAdapter.setNotifications(
+                mNotificationGroupsShown, /* setRecyclerViewListHeadersAndFooters= */ false);
+        updateExpansionIcon(/* isExpanded= */ true);
+        updateOnClickListener(/* isExpanded= */ true);
+    }
+
+    /**
+     * Collapses the {@link GroupNotificationViewHolder}.
+     */
+    public void collapseGroup() {
+        mExpandedGroupHeader.setVisibility(View.GONE);
+        // hide header divider
+        mHeaderDividerView.setVisibility(View.GONE);
+
+        NotificationGroup newGroup = new NotificationGroup();
+        newGroup.setSeen(mNotificationGroup.isSeen());
+
+        if (mUseLauncherIcon) {
+            // Only show first notification since notification header is not being used.
+            newGroup.addNotification(mNotificationGroup.getChildNotifications().get(0));
+            mNumberOfShownNotifications = 1;
+        } else {
+            // Only show group summary notification
+            newGroup.addNotification(mNotificationGroup.getGroupSummaryNotification());
+            // If the group summary notification is automatically generated,
+            // it does not contain a summary of the titles of the child notifications.
+            // Therefore, we generate a list of the child notification titles from
+            // the parent notification group, and pass them on.
+            newGroup.setChildTitles(mNotificationGroup.generateChildTitles());
+            mNumberOfShownNotifications = 0;
+        }
+
+        mNotificationGroupsShown = new ArrayList(Collections.singleton(newGroup));
+        mAdapter.setNotifications(
+                mNotificationGroupsShown, /* setRecyclerViewListHeadersAndFooters= */ false);
+
+        updateExpansionIcon(/* isExpanded= */ false);
+        updateOnClickListener(/* isExpanded= */ false);
+    }
+
     private void updateExpansionIcon(boolean isExpanded) {
         // expansion button in the group header
         if (mNotificationGroup.getChildCount() == 0) {
@@ -301,14 +327,19 @@
         updateDismissButton(getAlertEntry(), /* isHeadsUp= */ false);
     }
 
-    private void updateOnClickListener(CarNotificationViewAdapter parentAdapter,
-            boolean isExpanded) {
+    private void updateOnClickListener(boolean isExpanded) {
 
         View.OnClickListener expansionClickListener = view -> {
             boolean isExpanding = !isExpanded;
-            parentAdapter.setExpanded(mNotificationGroup.getGroupKey(), mNotificationGroup.isSeen(),
+            mParentAdapter.setExpanded(mNotificationGroup.getGroupKey(),
+                    mNotificationGroup.isSeen(),
                     isExpanding);
-            mAdapter.notifyDataSetChanged();
+            if (isExpanding) {
+                expandGroup();
+                addNotifications();
+            } else {
+                collapseGroup();
+            }
             if (!itemView.isInTouchMode()) {
                 if (isExpanding) {
                     mCurrentFocusRequestState = FocusRequestStates.EXPANDED_GROUP_HEADER;
@@ -330,12 +361,7 @@
             } else {
                 mCurrentFocusRequestState = FocusRequestStates.NONE;
             }
-            mNumberOfShownNotifications =
-                    addNextPageOfNotificationsToList(mNotificationGroupsShown);
-            mAdapter.setNotifications(mNotificationGroupsShown,
-                    /* setRecyclerViewListHeadersAndFooters= */ false);
-            updateExpansionIcon(isExpanded);
-            updateOnClickListener(parentAdapter, isExpanded);
+            addNotifications();
         };
 
         if (isExpanded) {
@@ -458,4 +484,9 @@
         CARD_VIEW,
         NONE,
     }
+
+    @VisibleForTesting
+    void setAdapter(CarNotificationViewAdapter adapter) {
+        mAdapter = adapter;
+    }
 }
diff --git a/src/com/android/car/notification/template/InboxNotificationViewHolder.java b/src/com/android/car/notification/template/InboxNotificationViewHolder.java
index 247860e..85f1a22 100644
--- a/src/com/android/car/notification/template/InboxNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/InboxNotificationViewHolder.java
@@ -21,7 +21,6 @@
 
 import com.android.car.notification.AlertEntry;
 import com.android.car.notification.NotificationClickHandlerFactory;
-import com.android.car.notification.NotificationUtils;
 import com.android.car.notification.R;
 
 /**
@@ -63,11 +62,9 @@
         Bundle extraData = notification.extras;
         CharSequence title = extraData.getCharSequence(Notification.EXTRA_TITLE_BIG);
         CharSequence text = extraData.getCharSequence(Notification.EXTRA_SUMMARY_TEXT);
-        boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(),
-                alertEntry.getStatusBarNotification());
 
-        mBodyView.bind(title, text, useLauncherIcon,
-                loadAppLauncherIcon(alertEntry.getStatusBarNotification()),
+        mBodyView.bind(title, text,
+                alertEntry.getStatusBarNotification(),
                 notification.getLargeIcon(), /* titleIcon= */ null, /* countText= */ null,
                 notification.showsTime() ? notification.when : null);
     }
diff --git a/src/com/android/car/notification/template/MessageNotificationViewHolder.java b/src/com/android/car/notification/template/MessageNotificationViewHolder.java
index 54efd59..f854e10 100644
--- a/src/com/android/car/notification/template/MessageNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/MessageNotificationViewHolder.java
@@ -34,7 +34,6 @@
 
 import com.android.car.notification.AlertEntry;
 import com.android.car.notification.NotificationClickHandlerFactory;
-import com.android.car.notification.NotificationUtils;
 import com.android.car.notification.PreprocessingManager;
 import com.android.car.notification.R;
 
@@ -226,11 +225,8 @@
                             sbn, conversationTitle, avatar, groupIcon, when);
             mBodyView.setCountOnClickListener(listener);
         }
-        boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(), sbn);
-
-        mBodyView.bind(conversationTitle, messageText, useLauncherIcon,
-                loadAppLauncherIcon(sbn), avatar, groupIcon,
-                unshownCountText, when);
+        mBodyView.bind(conversationTitle, messageText,
+                sbn, avatar, groupIcon, unshownCountText, when);
     }
 
     private CharSequence getMessageText(Notification.MessagingStyle.Message message,
@@ -400,10 +396,7 @@
                         R.plurals.message_unshown_count, finalUnshownCount, finalUnshownCount);
             }
 
-            Drawable launcherIcon = loadAppLauncherIcon(sbn);
-            boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(),
-                    sbn);
-            mBodyView.bind(title, finalMessage, useLauncherIcon, launcherIcon, avatar, groupIcon,
+            mBodyView.bind(title, finalMessage, sbn, avatar, groupIcon,
                     unshownCountText, when);
             mBodyView.setContentMaxLines(mMaxLineCount);
             mBodyView.setCountOnClickListener(null);
diff --git a/src/com/android/car/notification/template/NavigationNotificationViewHolder.java b/src/com/android/car/notification/template/NavigationNotificationViewHolder.java
index 605b72e..74d7fea 100644
--- a/src/com/android/car/notification/template/NavigationNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/NavigationNotificationViewHolder.java
@@ -21,7 +21,6 @@
 
 import com.android.car.notification.AlertEntry;
 import com.android.car.notification.NotificationClickHandlerFactory;
-import com.android.car.notification.NotificationUtils;
 import com.android.car.notification.R;
 
 /**
@@ -62,11 +61,8 @@
         Bundle extraData = notification.extras;
         CharSequence title = extraData.getCharSequence(Notification.EXTRA_TITLE);
         CharSequence text = extraData.getCharSequence(Notification.EXTRA_TEXT);
-        boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(),
-                alertEntry.getStatusBarNotification());
 
-        mBodyView.bind(title, text, useLauncherIcon,
-                loadAppLauncherIcon(alertEntry.getStatusBarNotification()),
+        mBodyView.bind(title, text, alertEntry.getStatusBarNotification(),
                 notification.getLargeIcon(), /* titleIcon= */ null, /* countText= */ null,
                 notification.showsTime() ? notification.when : null);
     }
diff --git a/src/com/android/car/notification/template/ProgressNotificationViewHolder.java b/src/com/android/car/notification/template/ProgressNotificationViewHolder.java
index d29fe65..499ba89 100644
--- a/src/com/android/car/notification/template/ProgressNotificationViewHolder.java
+++ b/src/com/android/car/notification/template/ProgressNotificationViewHolder.java
@@ -73,11 +73,9 @@
         Bundle extraData = notification.extras;
         CharSequence title = extraData.getCharSequence(Notification.EXTRA_TITLE);
         CharSequence text = extraData.getCharSequence(Notification.EXTRA_TEXT);
-        boolean useLauncherIcon = NotificationUtils.shouldUseLauncherIcon(getContext(),
-                alertEntry.getStatusBarNotification());
 
-        mBodyView.bind(title, text, useLauncherIcon,
-                loadAppLauncherIcon(alertEntry.getStatusBarNotification()),
+        mBodyView.bind(title, text,
+                alertEntry.getStatusBarNotification(),
                 notification.getLargeIcon(), /* titleIcon= */ null, /* countText= */ null,
                 notification.showsTime() ? notification.when : null);
 
diff --git a/tests/robotests/src/com/android/car/notification/TestNotificationApplication.java b/tests/robotests/src/com/android/car/notification/TestNotificationApplication.java
index 4a5c27b..7cd6a7b 100644
--- a/tests/robotests/src/com/android/car/notification/TestNotificationApplication.java
+++ b/tests/robotests/src/com/android/car/notification/TestNotificationApplication.java
@@ -39,7 +39,7 @@
  * the application.
  *
  * Therefore, all the initialization that we want to be affective on all the components should go
- * here. First the constructor will be called and the the on create of {@link
+ * here. First the constructor will be called and then the on create of {@link
  * NotificationApplication} will be called.
  *
  * Also, Even before calling any test for component beforeTest() of this class will be executed.
diff --git a/tests/unit/src/com/android/car/notification/CarNotificationListenerTest.java b/tests/unit/src/com/android/car/notification/CarNotificationListenerTest.java
index b1fb824..8444477 100644
--- a/tests/unit/src/com/android/car/notification/CarNotificationListenerTest.java
+++ b/tests/unit/src/com/android/car/notification/CarNotificationListenerTest.java
@@ -301,6 +301,7 @@
         testingHeadsUpNotification(false);
         UserHandle userHandle = new UserHandle(CURRENT_USER_ID);
         when(mStatusBarNotification.getUser()).thenReturn(userHandle);
+        when(mStatusBarNotification.getOverrideGroupKey()).thenReturn(null);
         mCarNotificationListener.onNotificationPosted(mStatusBarNotification, mRankingMap);
         reset(mHandler);
 
diff --git a/tests/unit/src/com/android/car/notification/CarNotificationTypeItemTest.java b/tests/unit/src/com/android/car/notification/CarNotificationTypeItemTest.java
index e93785a..d603610 100644
--- a/tests/unit/src/com/android/car/notification/CarNotificationTypeItemTest.java
+++ b/tests/unit/src/com/android/car/notification/CarNotificationTypeItemTest.java
@@ -115,28 +115,17 @@
     }
 
     @Test
-    public void groupExpandedNotificationType_shouldHaveCorrectValues() {
+    public void groupNotificationType_shouldHaveCorrectValues() {
         CarNotificationTypeItem groupExpanded = CarNotificationTypeItem.of(
-                NotificationViewType.GROUP_EXPANDED);
+                NotificationViewType.GROUP);
         assertThat(groupExpanded.getNotificationType()).isEqualTo(
-                NotificationViewType.GROUP_EXPANDED);
+                NotificationViewType.GROUP);
 
         assertProperties(groupExpanded, NO_TEMPLATE, R.layout.group_notification_template,
                 GroupNotificationViewHolder.class);
     }
 
     @Test
-    public void groupCollapsedNotificationType_shouldHaveCorrectValues() {
-        CarNotificationTypeItem groupCollapsed = CarNotificationTypeItem.of(
-                NotificationViewType.GROUP_COLLAPSED);
-        assertThat(groupCollapsed.getNotificationType()).isEqualTo(
-                NotificationViewType.GROUP_COLLAPSED);
-
-        assertProperties(groupCollapsed, NO_TEMPLATE, R.layout.group_notification_template,
-                GroupNotificationViewHolder.class);
-    }
-
-    @Test
     public void groupSummaryNotificationType_shouldHaveCorrectValues() {
         CarNotificationTypeItem groupSummary = CarNotificationTypeItem.of(
                 NotificationViewType.GROUP_SUMMARY);
diff --git a/tests/unit/src/com/android/car/notification/CarNotificationViewAdapterTest.java b/tests/unit/src/com/android/car/notification/CarNotificationViewAdapterTest.java
index 4d9366c..92d8ef3 100644
--- a/tests/unit/src/com/android/car/notification/CarNotificationViewAdapterTest.java
+++ b/tests/unit/src/com/android/car/notification/CarNotificationViewAdapterTest.java
@@ -37,6 +37,7 @@
 import android.testing.TestableResources;
 import android.view.View;
 
+import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -152,30 +153,19 @@
     }
 
     @Test
-    public void onCreateViewHolder_groupExpandedType_shouldReturnObjectOfGroupNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+    public void onCreateViewHolder_groupType_shouldReturnObjectOfGroupNotificationViewHolder() {
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
-                NotificationViewType.GROUP_EXPANDED);
-
-        assertThat(vh.getClass()).isEqualTo(GroupNotificationViewHolder.class);
-    }
-
-    @Test
-    public void onCreateViewHolder_groupCollapsed_shouldReturnObjectOfGroupNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
-                /* isGroupNotificationAdapter= */ false,
-                /* notificationItemController= */ null);
-        RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
-                NotificationViewType.GROUP_COLLAPSED);
+                NotificationViewType.GROUP);
 
         assertThat(vh.getClass()).isEqualTo(GroupNotificationViewHolder.class);
     }
 
     @Test
     public void onCreateViewHolder_groupSummaryType_shouldReturnObjectOfGroupSummaryNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -186,7 +176,7 @@
 
     @Test
     public void onCreateViewHolder_carInformation_shouldReturnObjectOfBasicNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -197,7 +187,7 @@
 
     @Test
     public void onCreateViewHolder_carInfoInGroup_shouldReturnObjectOfBasicNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -208,7 +198,7 @@
 
     @Test
     public void onCreateViewHolder_shouldReturnObjectOfMessageNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -219,7 +209,7 @@
 
     @Test
     public void onCreateViewHolder_message_shouldReturnObjectOfMessageNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -230,7 +220,7 @@
 
     @Test
     public void onCreateViewHolder_progressInGroupType_shouldReturnObjectOfProgressNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -241,7 +231,7 @@
 
     @Test
     public void onCreateViewHolder_progressInGroup_shouldReturnObjectOfProgressNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -252,7 +242,7 @@
 
     @Test
     public void onCreateViewHolder_inboxInGroupType_shouldReturnObjectOfInboxNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -263,7 +253,7 @@
 
     @Test
     public void onCreateViewHolder_inbox_shouldReturnObjectOfInboxNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -274,7 +264,7 @@
 
     @Test
     public void onCreateViewHolder_basicInGroup_shouldReturnObjectOfBasicNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -285,7 +275,7 @@
 
     @Test
     public void onCreateViewHolder_basic_shouldReturnObjectOfBasicNotificationViewHolder() {
-               mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
+        mCarNotificationViewAdapter = new CarNotificationViewAdapter(mContext,
                 /* isGroupNotificationAdapter= */ false,
                 /* notificationItemController= */ null);
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.onCreateViewHolder(null,
@@ -337,22 +327,7 @@
     }
 
     @Test
-    public void onBindViewHolder_groupExpanded_shouldNotThrowError() {
-        initializeWithFactory();
-
-        NotificationGroup notificationGroup = new NotificationGroup();
-        notificationGroup.setGroupSummaryNotification(mNotification1);
-        mNotificationGroupList1.add(notificationGroup);
-        mCarNotificationViewAdapter.setNotifications(
-                mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
-
-        RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.createViewHolder(null,
-                NotificationViewType.GROUP_EXPANDED);
-        mCarNotificationViewAdapter.onBindViewHolder(vh, 2);
-    }
-
-    @Test
-    public void onBindViewHolder_groupCollapsed_shouldNotThrowError() {
+    public void onBindViewHolder_group_shouldNotThrowError() {
         initializeWithFactory();
 
         NotificationGroup notificationGroup = new NotificationGroup();
@@ -363,7 +338,7 @@
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
 
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.createViewHolder(null,
-                NotificationViewType.GROUP_COLLAPSED);
+                NotificationViewType.GROUP);
         mCarNotificationViewAdapter.onBindViewHolder(vh, 2);
     }
 
@@ -537,23 +512,7 @@
     }
 
     @Test
-    public void onBindViewHolder_groupExpanded_shouldNotHideDismissButton() {
-        initializeWithFactory();
-
-        NotificationGroup notificationGroup = new NotificationGroup();
-        notificationGroup.setGroupSummaryNotification(mNotification1);
-        mNotificationGroupList1.add(notificationGroup);
-        mCarNotificationViewAdapter.setNotifications(
-                mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
-
-        RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.createViewHolder(null,
-                NotificationViewType.GROUP_EXPANDED);
-        mCarNotificationViewAdapter.onBindViewHolder(vh, 2);
-        assertThat(((CarNotificationBaseViewHolder) vh).shouldHideDismissButton()).isFalse();
-    }
-
-    @Test
-    public void onBindViewHolder_groupCollapsed_shouldNotHideDismissButton() {
+    public void onBindViewHolder_group_shouldNotHideDismissButton() {
         initializeWithFactory();
 
         NotificationGroup notificationGroup = new NotificationGroup();
@@ -564,7 +523,7 @@
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
 
         RecyclerView.ViewHolder vh = mCarNotificationViewAdapter.createViewHolder(null,
-                NotificationViewType.GROUP_COLLAPSED);
+                NotificationViewType.GROUP);
         mCarNotificationViewAdapter.onBindViewHolder(vh, 2);
         assertThat(((CarNotificationBaseViewHolder) vh).shouldHideDismissButton()).isFalse();
     }
@@ -586,7 +545,7 @@
     }
 
     @Test
-    public void getItemViewType_shouldReturnGroupCollapsed() {
+    public void getItemViewType_shouldReturnGroup() {
         initializeWithFactory();
         NotificationGroup notificationGroup = new NotificationGroup();
         notificationGroup.setGroupSummaryNotification(mNotification1);
@@ -595,29 +554,10 @@
         mNotificationGroupList1.add(notificationGroup);
         mCarNotificationViewAdapter.setNotifications(
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
-        mCarNotificationViewAdapter.setExpanded(notificationGroup.getGroupKey(),
-                /* isSeen= */ false, /* isExpanded= */ false);
 
         int itemViewType = mCarNotificationViewAdapter.getItemViewType(2);
 
-        assertThat(itemViewType).isEqualTo(NotificationViewType.GROUP_COLLAPSED);
-    }
-
-    @Test
-    public void getItemViewType_shouldReturnGroupExpanded() {
-        initializeWithFactory();
-        NotificationGroup notificationGroup = new NotificationGroup();
-        notificationGroup.setGroupSummaryNotification(mNotification1);
-        notificationGroup.addNotification(mNotification1);
-        notificationGroup.addNotification(mNotification1);
-        mNotificationGroupList1.add(notificationGroup);
-        mCarNotificationViewAdapter.setNotifications(
-                mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
-        mCarNotificationViewAdapter.setExpanded(notificationGroup.getGroupKey(),
-                /* isSeen= */ false, /* isExpanded= */ true);
-        int itemViewType = mCarNotificationViewAdapter.getItemViewType(2);
-
-        assertThat(itemViewType).isEqualTo(NotificationViewType.GROUP_EXPANDED);
+        assertThat(itemViewType).isEqualTo(NotificationViewType.GROUP);
     }
 
     @Test
@@ -626,7 +566,7 @@
 
         NotificationGroup notificationGroup = new NotificationGroup();
         notificationGroup.addNotification(
-                getNotificationWithCategory(Notification.CATEGORY_CAR_EMERGENCY));
+                generateNotificationWithCategory(Notification.CATEGORY_CAR_EMERGENCY));
         mNotificationGroupList1.add(notificationGroup);
         mCarNotificationViewAdapter.setNotifications(
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
@@ -642,7 +582,7 @@
 
         NotificationGroup notificationGroup = new NotificationGroup();
         notificationGroup.addNotification(
-                getNotificationWithCategory(Notification.CATEGORY_CAR_WARNING));
+                generateNotificationWithCategory(Notification.CATEGORY_CAR_WARNING));
         mNotificationGroupList1.add(notificationGroup);
         mCarNotificationViewAdapter.setNotifications(
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
@@ -658,7 +598,7 @@
 
         NotificationGroup notificationGroup = new NotificationGroup();
         notificationGroup.addNotification(
-                getNotificationWithCategory(Notification.CATEGORY_CAR_INFORMATION));
+                generateNotificationWithCategory(Notification.CATEGORY_CAR_INFORMATION));
         mNotificationGroupList1.add(notificationGroup);
         mCarNotificationViewAdapter.setNotifications(
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
@@ -674,7 +614,7 @@
 
         NotificationGroup notificationGroup = new NotificationGroup();
         notificationGroup.addNotification(
-                getNotificationWithCategory(Notification.CATEGORY_CAR_INFORMATION));
+                generateNotificationWithCategory(Notification.CATEGORY_CAR_INFORMATION));
         mNotificationGroupList1.add(notificationGroup);
         mCarNotificationViewAdapter.setNotifications(
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
@@ -690,7 +630,7 @@
 
         NotificationGroup notificationGroup = new NotificationGroup();
         notificationGroup.addNotification(
-                getNotificationWithCategory(Notification.CATEGORY_MESSAGE));
+                generateNotificationWithCategory(Notification.CATEGORY_MESSAGE));
         mNotificationGroupList1.add(notificationGroup);
         mCarNotificationViewAdapter.setNotifications(
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
@@ -706,7 +646,7 @@
 
         NotificationGroup notificationGroup = new NotificationGroup();
         notificationGroup.addNotification(
-                getNotificationWithCategory(Notification.CATEGORY_MESSAGE));
+                generateNotificationWithCategory(Notification.CATEGORY_MESSAGE));
         mNotificationGroupList1.add(notificationGroup);
         mCarNotificationViewAdapter.setNotifications(
                 mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
@@ -861,7 +801,6 @@
         assertThat(mCarNotificationViewAdapter.getItemCount()).isEqualTo(5);
     }
 
-
     @Test
     public void getViewPool_shouldReturnNotNull() {
         initializeWithFactory();
@@ -949,19 +888,79 @@
         assertThat(itemId).isEqualTo(notificationGroup.getSingleNotification().getKey().hashCode());
     }
 
-    private AlertEntry getNotificationWithCategory(String category) {
+    @Test
+    public void shouldRemoveGroupSummary_returnTrue_oneGroupWithSameGroupKeyIsPresent() {
+        String group = "TEST_GROUP";
+        initializeWithFactoryShowingRecentAndOlderHeaders();
+        AlertEntry groupSummaryNotification = generateNotification(Notification.CATEGORY_MESSAGE,
+                group, /* overrideGroupKey= */ null, /* isGroupSummary= */ true);
+        NotificationGroup notificationGroup = generateGroupedNotification(group);
+        notificationGroup.setGroupSummaryNotification(groupSummaryNotification);
+        mNotificationGroupList1.add(notificationGroup);
+        mCarNotificationViewAdapter.setNotifications(
+                mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
+
+        boolean result = mCarNotificationViewAdapter.shouldRemoveGroupSummary(
+                groupSummaryNotification.getStatusBarNotification().getGroupKey());
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void shouldRemoveGroupSummary_returnFalse_multipleGroupsWithSameGroupKeyArePresent() {
+        String group = "TEST_GROUP";
+        initializeWithFactoryShowingRecentAndOlderHeaders();
+        AlertEntry groupSummaryNotification = generateNotification(Notification.CATEGORY_MESSAGE,
+                group, /* overrideGroupKey= */ null, /* isGroupSummary= */ true);
+        NotificationGroup notificationGroup1 = generateGroupedNotification(group);
+        NotificationGroup notificationGroup2 = generateGroupedNotification(group);
+        notificationGroup1.setGroupSummaryNotification(groupSummaryNotification);
+        notificationGroup2.setGroupSummaryNotification(groupSummaryNotification);
+        mNotificationGroupList1.add(notificationGroup1);
+        mNotificationGroupList1.add(notificationGroup2);
+        mCarNotificationViewAdapter.setNotifications(
+                mNotificationGroupList1, /* setRecyclerViewListHeaderAndFooter= */ false);
+
+        boolean result = mCarNotificationViewAdapter.shouldRemoveGroupSummary(
+                groupSummaryNotification.getStatusBarNotification().getGroupKey());
+
+        assertThat(result).isFalse();
+    }
+
+
+    private NotificationGroup generateGroupedNotification(String group) {
+        NotificationGroup notificationGroup = new NotificationGroup();
+        for (int i = 0; i < 5; i++) {
+            notificationGroup.addNotification(generateNotification(Notification.CATEGORY_MESSAGE,
+                    group, /* overrideGroupKey= */ null,
+                    /* isGroupSummary= */ false));
+        }
+        return notificationGroup;
+    }
+
+    private AlertEntry generateNotificationWithCategory(String category) {
+        return generateNotification(category, /* group= */ null, OVERRIDE_GROUP_KEY,
+                /* isGroupSummary= */ false);
+    }
+
+    private AlertEntry generateNotification(String category, @Nullable String group,
+            @Nullable String overrideGroupKey, boolean isGroupSummary) {
         Notification.Builder nb = new Notification.Builder(mContext,
                 CHANNEL_ID)
                 .setContentTitle(CONTENT_TITLE)
                 .setCategory(category)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setGroupSummary(isGroupSummary);
+
+        if (group != null) {
+            nb.setGroup(group);
+        }
 
         return new AlertEntry(new StatusBarNotification(PKG_1, OP_PKG,
                 ID, TAG, UID, INITIAL_PID, nb.build(), USER_HANDLE,
-                OVERRIDE_GROUP_KEY, POST_TIME));
+                overrideGroupKey, POST_TIME));
     }
 
-
     private void initializeWithFactoryShowingRecentAndOlderHeaders() {
         initializeWithFactory(/* isGroup= */ false, /* showRecentAndOldHeaders= */ true);
     }
diff --git a/tests/unit/src/com/android/car/notification/CarNotificationViewTest.java b/tests/unit/src/com/android/car/notification/CarNotificationViewTest.java
index a053f4f..2e5dedc 100644
--- a/tests/unit/src/com/android/car/notification/CarNotificationViewTest.java
+++ b/tests/unit/src/com/android/car/notification/CarNotificationViewTest.java
@@ -19,8 +19,11 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.content.Context;
@@ -35,9 +38,12 @@
 import android.widget.Button;
 import android.widget.FrameLayout;
 
+import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.car.notification.template.GroupNotificationViewHolder;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -262,6 +268,36 @@
                 .isEqualTo(List.of(notDismissible).toString());
     }
 
+    @Test
+    public void resetState_collapseGroupForAllGroupsCalled() {
+        CarNotificationViewAdapter mockAdapter = mock(CarNotificationViewAdapter.class);
+        when(mockAdapter.getItemCount()).thenReturn(3);
+        GroupNotificationViewHolder mockGroupNotificationViewHolder1 = mock(
+                GroupNotificationViewHolder.class);
+        GroupNotificationViewHolder mockGroupNotificationViewHolder2 = mock(
+                GroupNotificationViewHolder.class);
+        when(mockGroupNotificationViewHolder1.getItemViewType()).thenReturn(
+                NotificationViewType.GROUP);
+        when(mockGroupNotificationViewHolder2.getItemViewType()).thenReturn(
+                NotificationViewType.GROUP);
+        RecyclerView mockListView = mock(RecyclerView.class);
+        when(mockListView.findViewHolderForAdapterPosition(0)).thenReturn(
+                mock(RecyclerView.ViewHolder.class));
+        when(mockListView.findViewHolderForAdapterPosition(1)).thenReturn(
+                mockGroupNotificationViewHolder1);
+        when(mockListView.findViewHolderForAdapterPosition(2)).thenReturn(
+                mockGroupNotificationViewHolder2);
+        mCarNotificationView.setAdapter(mockAdapter);
+        mCarNotificationView.setListView(mockListView);
+
+        mCarNotificationView.resetState();
+
+        verify(mockAdapter, times(1)).collapseAllGroups();
+        // only call resetNotifications on GroupNotificationViewHolders
+        verify(mockGroupNotificationViewHolder1, times(1)).collapseGroup();
+        verify(mockGroupNotificationViewHolder2, times(1)).collapseGroup();
+    }
+
     private NotificationGroup getNotificationGroup(boolean isDismissible) {
 
         Notification.Builder builder = new Notification.Builder(mContext,
diff --git a/tests/unit/src/com/android/car/notification/CarNotificationVisibilityLoggerTest.java b/tests/unit/src/com/android/car/notification/CarNotificationVisibilityLoggerTest.java
new file mode 100644
index 0000000..0fe97c8
--- /dev/null
+++ b/tests/unit/src/com/android/car/notification/CarNotificationVisibilityLoggerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.TestableContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+public class CarNotificationVisibilityLoggerTest {
+
+    private static final String PKG = "package_1";
+    private static final String OP_PKG = "OpPackage";
+    private static final int ID = 1;
+    private static final String TAG = "Tag";
+    private static final int UID = 2;
+    private static final int INITIAL_PID = 3;
+    private static final String CHANNEL_ID = "CHANNEL_ID";
+    private static final String CONTENT_TITLE = "CONTENT_TITLE";
+    private static final String OVERRIDE_GROUP_KEY = "OVERRIDE_GROUP_KEY";
+    private static final long POST_TIME = 12345L;
+    private static final UserHandle USER_HANDLE = new UserHandle(12);
+
+    @Rule
+    public TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext()) {
+        @Override
+        public Context createApplicationContext(ApplicationInfo application, int flags) {
+            return this;
+        }
+    };
+
+    @Mock
+    private IStatusBarService mBarService;
+    @Mock
+    private NotificationDataManager mNotificationDataManager;
+
+    private CarNotificationVisibilityLogger mNotificationVisibilityLogger;
+    private AlertEntry mMessageNotification;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(/* testClass= */this);
+
+        Notification.Builder mNotificationBuilder1 = new Notification.Builder(mContext, CHANNEL_ID)
+                .setContentTitle(CONTENT_TITLE);
+        mMessageNotification = new AlertEntry(new StatusBarNotification(PKG, OP_PKG,
+                ID, TAG, UID, INITIAL_PID, mNotificationBuilder1.build(), USER_HANDLE,
+                OVERRIDE_GROUP_KEY, POST_TIME));
+
+        when(mNotificationDataManager.getVisibleNotifications()).thenReturn(
+                Collections.singletonList(mMessageNotification));
+
+        mNotificationVisibilityLogger = new CarNotificationVisibilityLogger(
+                mBarService, mNotificationDataManager);
+    }
+
+    @Test
+    public void notificationVisibilityChanged_notifiesStatusBarService() throws RemoteException {
+        mNotificationVisibilityLogger.notifyVisibilityChanged(/* isVisible= */ true);
+
+        verify(mBarService).onNotificationVisibilityChanged(
+                any(NotificationVisibility[].class), any(NotificationVisibility[].class));
+    }
+
+    @Test
+    public void notificationVisibilityChanged_isVisibleTrue_notifiesOfNewlyVisibleItems()
+            throws RemoteException {
+        ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+        ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+
+        mNotificationVisibilityLogger.notifyVisibilityChanged(/* isVisible= */ true);
+
+        verify(mBarService).onNotificationVisibilityChanged(
+                newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
+        assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(1);
+        assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(0);
+    }
+
+    @Test
+    public void notificationVisibilityChanged_invisible_notifiesOfPreviouslyVisibleItems()
+            throws RemoteException {
+        ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+        ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+        mNotificationVisibilityLogger.notifyVisibilityChanged(/* isVisible= */ true);
+        reset(mBarService);
+
+        mNotificationVisibilityLogger.notifyVisibilityChanged(/* isVisible= */ false);
+
+        verify(mBarService).onNotificationVisibilityChanged(
+                newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
+        assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(1);
+        assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(0);
+    }
+}
diff --git a/tests/unit/src/com/android/car/notification/NotificationDataManagerTest.java b/tests/unit/src/com/android/car/notification/NotificationDataManagerTest.java
index b197d03..c38d576 100644
--- a/tests/unit/src/com/android/car/notification/NotificationDataManagerTest.java
+++ b/tests/unit/src/com/android/car/notification/NotificationDataManagerTest.java
@@ -180,7 +180,7 @@
     }
 
     @Test
-    public void setNotificationsAsSeen_notificationIsSeen() {
+    public void setVisibleNotificationsAsSeen_notificationIsSeen() {
         List<NotificationGroup> notificationGroups = new ArrayList<>();
 
         NotificationGroup notificationGroup = new NotificationGroup();
@@ -188,7 +188,7 @@
         notificationGroups.add(notificationGroup);
 
         mNotificationDataManager.updateUnseenNotificationGroups(notificationGroups);
-        mNotificationDataManager.setNotificationsAsSeen(
+        mNotificationDataManager.setVisibleNotificationsAsSeen(
                 Collections.singletonList(mMessageNotification));
 
         assertThat(mNotificationDataManager.getSeenNotifications()).asList().containsExactly(
@@ -204,7 +204,7 @@
         notificationGroups.add(notificationGroup);
 
         mNotificationDataManager.updateUnseenNotificationGroups(notificationGroups);
-        mNotificationDataManager.setNotificationsAsSeen(
+        mNotificationDataManager.setVisibleNotificationsAsSeen(
                 Collections.singletonList(mMessageNotification));
 
         assertThat(mNotificationDataManager.isNotificationSeen(mMessageNotification)).isTrue();
@@ -222,7 +222,7 @@
         notificationGroups.add(notificationGroup2);
 
         mNotificationDataManager.updateUnseenNotificationGroups(notificationGroups);
-        mNotificationDataManager.setNotificationsAsSeen(
+        mNotificationDataManager.setVisibleNotificationsAsSeen(
                 Collections.singletonList(mMessageNotification));
 
         assertThat(mNotificationDataManager.isNotificationSeen(mNonMessageNotification)).isFalse();
@@ -242,7 +242,7 @@
     }
 
     @Test
-    public void setNotificationsAsSeen_notificationIsSeen_decrementsUnseenCount() {
+    public void setVisibleNotificationsAsSeen_notificationIsSeen_decrementsUnseenCount() {
         List<NotificationGroup> notificationGroups = new ArrayList<>();
 
         NotificationGroup notificationGroup = new NotificationGroup();
@@ -253,7 +253,7 @@
                         Collections.singletonList(IMPORTANCE_HIGH));
 
         mNotificationDataManager.updateUnseenNotificationGroups(notificationGroups);
-        mNotificationDataManager.setNotificationsAsSeen(
+        mNotificationDataManager.setVisibleNotificationsAsSeen(
                 Collections.singletonList(mMessageNotification));
 
         assertThat(mNotificationDataManager.getNonLowImportanceUnseenNotificationCount(rankingMap))
@@ -261,7 +261,7 @@
     }
 
     @Test
-    public void setNotificationsAsSeen_notificationIsSeen_notificationIsVisibleToUser() {
+    public void setVisibleNotificationsAsSeen_notificationIsSeen_notificationIsVisibleToUser() {
         List<NotificationGroup> notificationGroups = new ArrayList<>();
 
         NotificationGroup notificationGroup = new NotificationGroup();
@@ -269,7 +269,7 @@
         notificationGroups.add(notificationGroup);
 
         mNotificationDataManager.updateUnseenNotificationGroups(notificationGroups);
-        mNotificationDataManager.setNotificationsAsSeen(
+        mNotificationDataManager.setVisibleNotificationsAsSeen(
                 Collections.singletonList(mMessageNotification));
 
         assertThat(mNotificationDataManager.getVisibleNotifications()).containsExactly(
diff --git a/tests/unit/src/com/android/car/notification/NotificationUtilsTest.java b/tests/unit/src/com/android/car/notification/NotificationUtilsTest.java
index ac12172..24fd62d 100644
--- a/tests/unit/src/com/android/car/notification/NotificationUtilsTest.java
+++ b/tests/unit/src/com/android/car/notification/NotificationUtilsTest.java
@@ -187,50 +187,6 @@
                 .isFalse();
     }
 
-
-    @Test
-    public void onShouldUseLauncherIcon_noExtras_returnsDefault()
-            throws PackageManager.NameNotFoundException {
-        setApplicationInfo(/* signedWithPlatformKey= */ false, /* isSystemApp= */
-                true, /* isPrivilegedApp= */ false);
-        Notification notification = new Notification();
-        notification.extras = new Bundle();
-        when(mStatusBarNotification.getNotification()).thenReturn(notification);
-
-        assertThat(NotificationUtils.shouldUseLauncherIcon(mContext, mStatusBarNotification))
-                .isTrue();
-    }
-
-    @Test
-    public void onShouldUseLauncherIcon_notSystemApp_returnsDefault()
-            throws PackageManager.NameNotFoundException {
-        setApplicationInfo(/* signedWithPlatformKey= */ false, /* isSystemApp= */
-                false, /* isPrivilegedApp= */ false);
-        Notification notification = new Notification();
-        notification.extras = new Bundle();
-        notification.extras.putBoolean(
-                NotificationUtils.EXTRA_USE_LAUNCHER_ICON, false);
-        when(mStatusBarNotification.getNotification()).thenReturn(notification);
-
-        assertThat(NotificationUtils.shouldUseLauncherIcon(mContext, mStatusBarNotification))
-                .isTrue();
-    }
-
-    @Test
-    public void onShouldUseLauncherIcon_systemApp_returnsExtra()
-            throws PackageManager.NameNotFoundException {
-        setApplicationInfo(/* signedWithPlatformKey= */ false, /* isSystemApp= */
-                true, /* isPrivilegedApp= */ false);
-        Notification notification = new Notification();
-        notification.extras = new Bundle();
-        notification.extras.putBoolean(
-                NotificationUtils.EXTRA_USE_LAUNCHER_ICON, false);
-        when(mStatusBarNotification.getNotification()).thenReturn(notification);
-
-        assertThat(NotificationUtils.shouldUseLauncherIcon(mContext, mStatusBarNotification))
-                .isFalse();
-    }
-
     @Test
     public void onGetNotificationViewType_notificationIsARecognizedType_returnsCorrectType() {
         Map<String, CarNotificationTypeItem> typeMap = new HashMap<>();
diff --git a/tests/unit/src/com/android/car/notification/PreprocessingManagerTest.java b/tests/unit/src/com/android/car/notification/PreprocessingManagerTest.java
index e95045e..2176fa7 100644
--- a/tests/unit/src/com/android/car/notification/PreprocessingManagerTest.java
+++ b/tests/unit/src/com/android/car/notification/PreprocessingManagerTest.java
@@ -47,6 +47,7 @@
 import android.telephony.TelephonyManager;
 import android.testing.TestableContext;
 import android.testing.TestableResources;
+import android.text.TextUtils;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -55,6 +56,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -715,7 +717,7 @@
 
     @Test
     public void onAdditionalGroupAndRank_isGroupSummary_returnsTheSameGroupsAsStandardGroup() {
-        Notification additionalNotification = generateNotification(/* isForeground= */ true,
+        Notification additionalNotification = generateNotification(/* isForeground= */ false,
                 /* isNavigation= */ false, /* isGroupSummary= */ true);
         additionalNotification.category = Notification.CATEGORY_MESSAGE;
         when(mAdditionalStatusBarNotification.getKey()).thenReturn("ADDITIONAL");
@@ -771,7 +773,7 @@
         mAlertEntries.add(first);
 
         List<NotificationGroup> additionalRanked = mPreprocessingManager.additionalGroupAndRank(
-                newEntry, generateRankingMap(mAlertEntries), /* isUpdate= */ false)
+                        newEntry, generateRankingMap(mAlertEntries), /* isUpdate= */ false)
                 .stream()
                 .filter(g -> !g.getGroupKey().equals(groupKey))
                 .collect(Collectors.toList());
@@ -834,6 +836,140 @@
     }
 
     @Test
+    public void onAdditionalGroupAndRank_newNotification_setAsSeenInDataManger() {
+        String key = "TEST_KEY";
+        mPreprocessingManager.setNotificationDataManager(mNotificationDataManager);
+        mPreprocessingManager.init(mAlertEntriesMap, mRankingMap);
+        Notification newNotification = generateNotification(/* isForeground= */ false,
+                /* isNavigation= */ false, /* isGroupSummary= */ false);
+        StatusBarNotification newSbn = mock(StatusBarNotification.class);
+        when(newSbn.getNotification()).thenReturn(newNotification);
+        when(newSbn.getKey()).thenReturn(key);
+        when(newSbn.getGroupKey()).thenReturn("groupKey");
+        AlertEntry newEntry = new AlertEntry(newSbn);
+
+        mPreprocessingManager.additionalGroupAndRank(newEntry,
+                generateRankingMap(mAlertEntries), /* isUpdate= */ false);
+
+        ArgumentCaptor<AlertEntry> arg = ArgumentCaptor.forClass(AlertEntry.class);
+        verify(mNotificationDataManager).setNotificationAsSeen(arg.capture());
+        assertThat(arg.getValue().getKey()).isEqualTo(key);
+    }
+
+    @Test
+    public void onAdditionalGroupAndRank_addToExistingGroup_groupSurpassGroupingThresholdExist() {
+        String key = "TEST_KEY";
+        String groupKey = "TEST_GROUP_KEY";
+        int numberOfGroupNotifications = 5;
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_minimumGroupingThreshold, /* value= */ 4);
+        generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey);
+        generateGroupSummaryNotification(groupKey);
+        mPreprocessingManager.init(mAlertEntriesMap, mRankingMap);
+        Notification newNotification = generateNotification(/* isForeground= */ false,
+                /* isNavigation= */ false, /* isGroupSummary= */ false);
+        StatusBarNotification newSbn = mock(StatusBarNotification.class);
+        when(newSbn.getNotification()).thenReturn(newNotification);
+        when(newSbn.getKey()).thenReturn(key);
+        when(newSbn.getGroupKey()).thenReturn(groupKey);
+        AlertEntry newEntry = new AlertEntry(newSbn);
+
+        List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry,
+                generateRankingMap(mAlertEntries), /* isUpdate= */ false);
+
+        List<NotificationGroup> resultNotificationGroups = rawResult.stream()
+                .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey))
+                .collect(Collectors.toList());
+        assertThat(resultNotificationGroups.size()).isEqualTo(1);
+        List<AlertEntry> resultAlertEntries = resultNotificationGroups.get(0)
+                .getChildNotifications();
+        assertThat(resultAlertEntries.size()).isEqualTo(numberOfGroupNotifications + 1);
+        assertThat(resultAlertEntries.get(resultAlertEntries.size() - 1).getKey()).isEqualTo(key);
+    }
+
+    @Test
+    public void onAdditionalGroupAndRank_addNewNotification_notSurpassGroupingThreshold() {
+        String key = "TEST_KEY";
+        String groupKey = "TEST_GROUP_KEY";
+        int numberOfGroupNotifications = 2;
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_minimumGroupingThreshold, /* value= */ 4);
+        generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey);
+        generateGroupSummaryNotification(groupKey);
+        mPreprocessingManager.init(mAlertEntriesMap, mRankingMap);
+        Notification newNotification = generateNotification(/* isForeground= */ false,
+                /* isNavigation= */ false, /* isGroupSummary= */ false);
+        StatusBarNotification newSbn = mock(StatusBarNotification.class);
+        when(newSbn.getNotification()).thenReturn(newNotification);
+        when(newSbn.getKey()).thenReturn(key);
+        when(newSbn.getGroupKey()).thenReturn(groupKey);
+        AlertEntry newEntry = new AlertEntry(newSbn);
+
+        List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry,
+                generateRankingMap(mAlertEntries), /* isUpdate= */ false);
+
+        List<NotificationGroup> resultNotificationGroups = rawResult.stream()
+                .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey))
+                .collect(Collectors.toList());
+        assertThat(resultNotificationGroups.size()).isEqualTo(numberOfGroupNotifications + 1);
+    }
+
+    @Test
+    public void onAdditionalGroupAndRank_createsNewGroup_surpassGroupingThreshold() {
+        String key = "TEST_KEY";
+        String groupKey = "TEST_GROUP_KEY";
+        int numberOfGroupNotifications = 3;
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_minimumGroupingThreshold, /* value= */ 4);
+        generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey);
+        generateGroupSummaryNotification(groupKey);
+        mPreprocessingManager.init(mAlertEntriesMap, mRankingMap);
+        Notification newNotification = generateNotification(/* isForeground= */ false,
+                /* isNavigation= */ false, /* isGroupSummary= */ false);
+        StatusBarNotification newSbn = mock(StatusBarNotification.class);
+        when(newSbn.getNotification()).thenReturn(newNotification);
+        when(newSbn.getKey()).thenReturn(key);
+        when(newSbn.getGroupKey()).thenReturn(groupKey);
+        AlertEntry newEntry = new AlertEntry(newSbn);
+
+        List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry,
+                generateRankingMap(mAlertEntries), /* isUpdate= */ false);
+
+        List<NotificationGroup> resultNotificationGroups = rawResult.stream()
+                .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey))
+                .collect(Collectors.toList());
+        assertThat(resultNotificationGroups.size()).isEqualTo(1);
+        assertThat(resultNotificationGroups.get(0).getChildCount())
+                .isEqualTo(numberOfGroupNotifications + 1);
+    }
+
+    @Test
+    public void onAdditionalGroupAndRank_doesNotGroup_groupSummaryMissing() {
+        String key = "TEST_KEY";
+        String groupKey = "TEST_GROUP_KEY";
+        int numberOfGroupNotifications = 3;
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.config_minimumGroupingThreshold, /* value= */ 4);
+        generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey);
+        mPreprocessingManager.init(mAlertEntriesMap, mRankingMap);
+        Notification newNotification = generateNotification(/* isForeground= */ false,
+                /* isNavigation= */ false, /* isGroupSummary= */ false);
+        StatusBarNotification newSbn = mock(StatusBarNotification.class);
+        when(newSbn.getNotification()).thenReturn(newNotification);
+        when(newSbn.getKey()).thenReturn(key);
+        when(newSbn.getGroupKey()).thenReturn(groupKey);
+        AlertEntry newEntry = new AlertEntry(newSbn);
+
+        List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry,
+                generateRankingMap(mAlertEntries), /* isUpdate= */ false);
+
+        List<NotificationGroup> resultNotificationGroups = rawResult.stream()
+                .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey))
+                .collect(Collectors.toList());
+        assertThat(resultNotificationGroups.size()).isEqualTo(numberOfGroupNotifications + 1);
+    }
+
+    @Test
     public void onUpdateNotifications_notificationRemoved_removesNotification() {
         mPreprocessingManager.init(mAlertEntriesMap, mRankingMap);
 
@@ -989,6 +1125,7 @@
                 .build();
 
         if (isForeground) {
+            // this will reset flags previously set like FLAG_GROUP_SUMMARY
             notification.flags = Notification.FLAG_FOREGROUND_SERVICE;
         }
 
@@ -1056,6 +1193,33 @@
         return rankingMap;
     }
 
+    private void generateNotificationsWithSameGroupKey(int numberOfNotifications, String groupKey) {
+        for (int i = 0; i < numberOfNotifications; i++) {
+            String key = "BASE_KEY_" + i;
+            Notification notification = generateNotification(/* isForeground= */ false,
+                    /* isNavigation= */ false, /* isGroupSummary= */ false);
+            StatusBarNotification sbn = mock(StatusBarNotification.class);
+            when(sbn.getNotification()).thenReturn(notification);
+            when(sbn.getKey()).thenReturn(key);
+            when(sbn.getGroupKey()).thenReturn(groupKey);
+            AlertEntry alertEntry = new AlertEntry(sbn);
+            mAlertEntries.add(alertEntry);
+            mAlertEntriesMap.put(alertEntry.getKey(), alertEntry);
+        }
+    }
+
+    private void generateGroupSummaryNotification(String groupKey) {
+        Notification groupSummary = generateNotification(/* isForeground= */ false,
+                /* isNavigation= */ false, /* isGroupSummary= */ true);
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getNotification()).thenReturn(groupSummary);
+        when(sbn.getKey()).thenReturn("KEY_GROUP_SUMMARY");
+        when(sbn.getGroupKey()).thenReturn(groupKey);
+        AlertEntry alertEntry = new AlertEntry(sbn);
+        mAlertEntries.add(alertEntry);
+        mAlertEntriesMap.put(alertEntry.getKey(), alertEntry);
+    }
+
     private int getVisibilityOverride(int index) {
         return index * 9;
     }
diff --git a/tests/unit/src/com/android/car/notification/headsup/HeadsUpContainerViewTestActivity.java b/tests/unit/src/com/android/car/notification/headsup/HeadsUpContainerViewTestActivity.java
index 7c8f82e..a118a69 100644
--- a/tests/unit/src/com/android/car/notification/headsup/HeadsUpContainerViewTestActivity.java
+++ b/tests/unit/src/com/android/car/notification/headsup/HeadsUpContainerViewTestActivity.java
@@ -18,17 +18,27 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.testing.TestableContext;
 
 import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.notification.R;
+
+import org.junit.Rule;
 
 public class HeadsUpContainerViewTestActivity extends Activity {
     private HeadsUpContainerView mHeadsUpContainerView;
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext());
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        mHeadsUpContainerView = new HeadsUpContainerView(this);
+        mContext.getOrCreateTestableResources().addOverride(R.bool.config_focusHUNWhenShown,
+                /* value= */ false);
+        mHeadsUpContainerView = new HeadsUpContainerView(mContext);
         setContentView(mHeadsUpContainerView);
     }
 
diff --git a/tests/unit/src/com/android/car/notification/template/CarNotificationBodyViewTest.java b/tests/unit/src/com/android/car/notification/template/CarNotificationBodyViewTest.java
index 9001e60..d26f6da 100644
--- a/tests/unit/src/com/android/car/notification/template/CarNotificationBodyViewTest.java
+++ b/tests/unit/src/com/android/car/notification/template/CarNotificationBodyViewTest.java
@@ -18,9 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
+import android.service.notification.StatusBarNotification;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -28,6 +35,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Calendar;
@@ -43,6 +51,12 @@
 
     private CarNotificationBodyView mCarNotificationBodyView;
     private Context mContext;
+    @Mock
+    private StatusBarNotification mMockStatusBarNotification;
+    @Mock
+    private Notification mMockNotification;
+    @Mock
+    private Context mMockContext;
 
     @Before
     public void setup() {
@@ -50,12 +64,15 @@
         mContext = ApplicationProvider.getApplicationContext();
         mCarNotificationBodyView = new CarNotificationBodyView(mContext, /* attrs= */ null);
         mCarNotificationBodyView.onFinishInflate();
+        when(mMockStatusBarNotification.getNotification()).thenReturn(mMockNotification);
+        when(mMockContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+        when(mMockStatusBarNotification.getPackageContext(any())).thenReturn(mMockContext);
     }
 
     @Test
     public void onBind_launcherIconUsed_titleTextSet() {
-        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, /* useLauncherIcon= */ true,
-                TEST_DRAWABLE, /* largeIcon= */ null, /* titleIcon= */ null,
+        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, mMockStatusBarNotification,
+                /* largeIcon= */ null, /* titleIcon= */ null,
                 TEST_COUNT, TEST_WHEN);
 
         assertThat(mCarNotificationBodyView.getTitleView().getText()).isEqualTo(TEST_TITLE);
@@ -63,8 +80,8 @@
 
     @Test
     public void onBind_launcherIconUsed_contentTextSet() {
-        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, /* useLauncherIcon= */ true,
-                TEST_DRAWABLE, /* largeIcon= */ null, /* titleIcon= */ null,
+        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, mMockStatusBarNotification,
+                /* largeIcon= */ null, /* titleIcon= */ null,
                 TEST_COUNT, TEST_WHEN);
 
         assertThat(mCarNotificationBodyView.getContentView().getText()).isEqualTo(TEST_BODY);
@@ -72,8 +89,8 @@
 
     @Test
     public void onBind_launcherIconUsed_countTextSet() {
-        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, /* useLauncherIcon= */ true,
-                TEST_DRAWABLE, /* largeIcon= */ null, /* titleIcon= */ null,
+        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, mMockStatusBarNotification,
+                /* largeIcon= */ null, /* titleIcon= */ null,
                 TEST_COUNT, TEST_WHEN);
 
         assertThat(mCarNotificationBodyView.getCountView().getText()).isEqualTo(TEST_COUNT);
@@ -81,8 +98,8 @@
 
     @Test
     public void onBind_launcherIconUsed_timeSet() {
-        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, /* useLauncherIcon= */ true,
-                TEST_DRAWABLE, /* largeIcon= */ null, /* titleIcon= */ null,
+        mCarNotificationBodyView.bind(TEST_TITLE, TEST_BODY, mMockStatusBarNotification,
+                /* largeIcon= */ null, /* titleIcon= */ null,
                 TEST_COUNT, TEST_WHEN);
 
         assertThat(mCarNotificationBodyView.getTimeView().getText()).isEqualTo(EXPECTED_WHEN);
diff --git a/tests/unit/src/com/android/car/notification/template/GroupNotificationViewHolderTest.java b/tests/unit/src/com/android/car/notification/template/GroupNotificationViewHolderTest.java
index c1f9bf4..b83eae4 100644
--- a/tests/unit/src/com/android/car/notification/template/GroupNotificationViewHolderTest.java
+++ b/tests/unit/src/com/android/car/notification/template/GroupNotificationViewHolderTest.java
@@ -18,6 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.Notification;
@@ -163,6 +167,23 @@
                 .setExpanded(group.getGroupKey(), group.isSeen(), /* isExpanded= */ false);
     }
 
+    @Test
+    public void resetNotifications_removeAllGroupedNotifications() {
+        CarNotificationViewAdapter mockAdapter = mock(CarNotificationViewAdapter.class);
+        NotificationGroup group = getNotificationGroup(/* size= */ 22);
+        mGroupNotificationViewHolder.setAdapter(mockAdapter);
+        mGroupNotificationViewHolder
+                .bind(group, mCarNotificationViewAdapter, /* isExpanded= */ true);
+
+        mGroupNotificationViewHolder.collapseGroup();
+
+        // equal to 1 because the expected behaviour is to collapse the
+        // GroupNotificationViewHolder by removing all notifications and
+        // displaying to top notification or GroupNotificationSummary.
+        verify(mockAdapter, times(1)).setNotifications(
+                argThat(notificationGroupList -> notificationGroupList.size() == 1), eq(false));
+    }
+
     private NotificationGroup getNotificationGroup(int size) {
         Notification groupSummaryNotification = new MockMessageNotificationBuilder(
                 mContext, CHANNEL_ID, android.R.drawable.sym_def_app_icon)