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)