Merge "Delete NotifGroupManagerLegacy" into tm-qpr-dev am: 88b46e9016

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19735657

Change-Id: I792eb3e153e8eed082d14370e5aa92005059eb06
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Steve Elliott
2022-08-29 19:55:34 +00:00
committed by Automerger Merge Worker
9 changed files with 9 additions and 1411 deletions

View File

@@ -87,7 +87,6 @@ import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -311,7 +310,6 @@ public class Dependency {
@Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
@Inject Lazy<NotificationGroupManagerLegacy> mNotificationGroupManager;
@Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
@Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
@Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
@@ -524,7 +522,6 @@ public class Dependency {
mProviders.put(NotificationLockscreenUserManager.class,
mNotificationLockscreenUserManager::get);
mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get);
mProviders.put(NotificationGroupManagerLegacy.class, mNotificationGroupManager::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
mProviders.put(NotificationRemoteInputManager.class,

View File

@@ -16,9 +16,8 @@
package com.android.systemui.statusbar.notification.collection.inflation;
import android.annotation.Nullable;
import android.annotation.NonNull;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -35,22 +34,10 @@ public interface NotificationRowBinder {
*/
void inflateViews(
NotificationEntry entry,
NotifInflater.Params params,
@NonNull NotifInflater.Params params,
NotificationRowContentBinder.InflationCallback callback)
throws InflationException;
/**
* Called when the ranking has been updated (but not add or remove has been done). The binder
* should inspect the old and new adjustments and re-inflate the entry's views if necessary
* (e.g. if something important changed).
*/
void onNotificationRankingUpdated(
NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
NotificationUiAdjustment newAdjustment,
NotificationRowContentBinder.InflationCallback callback);
/**
* Called when a notification is no longer likely to be displayed and can have its views freed.
*/

View File

@@ -22,6 +22,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Build;
@@ -32,12 +33,9 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
@@ -68,8 +66,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
private final ExpandableNotificationRowComponent.Builder
mExpandableNotificationRowComponentBuilder;
private final IconManager mIconManager;
private final LowPriorityInflationHelper mLowPriorityInflationHelper;
private final NotifPipelineFlags mNotifPipelineFlags;
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
@@ -86,9 +82,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
RowContentBindStage rowContentBindStage,
Provider<RowInflaterTask> rowInflaterTaskProvider,
ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
IconManager iconManager,
LowPriorityInflationHelper lowPriorityInflationHelper,
NotifPipelineFlags notifPipelineFlags) {
IconManager iconManager) {
mContext = context;
mNotifBindPipeline = notifBindPipeline;
mRowContentBindStage = rowContentBindStage;
@@ -98,8 +92,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
mRowInflaterTaskProvider = rowInflaterTaskProvider;
mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
mIconManager = iconManager;
mLowPriorityInflationHelper = lowPriorityInflationHelper;
mNotifPipelineFlags = notifPipelineFlags;
}
/**
@@ -125,13 +117,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
@Override
public void inflateViews(
NotificationEntry entry,
NotifInflater.Params params,
@NonNull NotifInflater.Params params,
NotificationRowContentBinder.InflationCallback callback)
throws InflationException {
if (params == null) {
// weak assert that the params should always be passed in the new pipeline
mNotifPipelineFlags.checkLegacyPipelineEnabled();
}
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
if (entry.rowExists()) {
@@ -190,39 +178,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
mBindRowCallback.onBindRow(row);
}
/**
* Updates the views bound to an entry when the entry's ranking changes, either in-place or by
* reinflating them.
*
* TODO: Should this method be in this class?
*/
@Override
public void onNotificationRankingUpdated(
NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
NotificationUiAdjustment newAdjustment,
NotificationRowContentBinder.InflationCallback callback) {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) {
if (entry.rowExists()) {
ExpandableNotificationRow row = entry.getRow();
row.reset();
updateRow(entry, row);
inflateContentViews(entry, null, row, callback);
} else {
// Once the RowInflaterTask is done, it will pick up the updated entry, so
// no-op here.
}
} else {
if (oldImportance != null && entry.getImportance() != oldImportance) {
if (entry.rowExists()) {
entry.getRow().onNotificationRankingUpdated();
}
}
}
}
/**
* Update row after the notification has updated.
*
@@ -243,24 +198,12 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
*/
private void inflateContentViews(
NotificationEntry entry,
NotifInflater.Params inflaterParams,
@NonNull NotifInflater.Params inflaterParams,
ExpandableNotificationRow row,
@Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
final boolean useIncreasedCollapsedHeight =
mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
final boolean isLowPriority;
if (inflaterParams != null) {
// NEW pipeline
isLowPriority = inflaterParams.isLowPriority();
} else {
// LEGACY pipeline
mNotifPipelineFlags.checkLegacyPipelineEnabled();
// If this is our first time inflating, we don't actually know the groupings for real
// yet, so we might actually inflate a low priority content view incorrectly here and
// have to correct it later in the pipeline. On subsequent inflations (i.e. updates),
// this should inflate the correct view.
isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry);
}
final boolean isLowPriority = inflaterParams.isLowPriority();
RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);

View File

@@ -1,49 +0,0 @@
/*
* Copyright (C) 2020 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.systemui.statusbar.notification.collection.legacy;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import javax.inject.Inject;
/**
* Helper class that provide methods to help check when we need to inflate a low priority version
* ot notification content.
*/
@SysUISingleton
public class LowPriorityInflationHelper {
private final NotificationGroupManagerLegacy mGroupManager;
private final NotifPipelineFlags mNotifPipelineFlags;
@Inject
LowPriorityInflationHelper(
NotificationGroupManagerLegacy groupManager,
NotifPipelineFlags notifPipelineFlags) {
mGroupManager = groupManager;
mNotifPipelineFlags = notifPipelineFlags;
}
/**
* Whether the notification should inflate a low priority version of its content views.
*/
public boolean shouldUseLowPriorityView(NotificationEntry entry) {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return entry.isAmbient() && !mGroupManager.isChildInGroup(entry);
}
}

View File

@@ -1,950 +0,0 @@
/*
* Copyright (C) 2015 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.systemui.statusbar.notification.collection.legacy;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.util.Compile;
import com.android.wm.shell.bubbles.Bubbles;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeSet;
import java.util.function.Function;
import javax.inject.Inject;
import dagger.Lazy;
/**
* A class to handle notifications and their corresponding groups.
* This includes:
* 1. Determining whether an entry is a member of a group and whether it is a summary or a child
* 2. Tracking group expansion states
*/
@SysUISingleton
public class NotificationGroupManagerLegacy implements StateListener, Dumpable {
private static final String TAG = "LegacyNotifGroupManager";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
/**
* The maximum amount of time (in ms) between the posting of notifications that can be
* considered part of the same update batch.
*/
private static final long POST_BATCH_MAX_AGE = 5000;
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
private final Optional<Bubbles> mBubblesOptional;
private final GroupEventDispatcher mEventDispatcher = new GroupEventDispatcher(mGroupMap::get);
private final HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private boolean mIsUpdatingUnchangedGroup;
@Inject
public NotificationGroupManagerLegacy(
StatusBarStateController statusBarStateController,
Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier,
Optional<Bubbles> bubblesOptional,
DumpManager dumpManager) {
statusBarStateController.addCallback(this);
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesOptional = bubblesOptional;
dumpManager.registerDumpable(this);
}
/**
* Add a listener for changes to groups.
*/
public void registerGroupChangeListener(OnGroupChangeListener listener) {
mEventDispatcher.registerGroupChangeListener(listener);
}
private void setGroupExpanded(NotificationGroup group, boolean expanded) {
group.expanded = expanded;
}
/**
* When we want to remove an entry from being tracked for grouping
*/
public void onEntryRemoved(NotificationEntry removed) {
if (SPEW) {
Log.d(TAG, "onEntryRemoved: entry=" + logKey(removed));
}
mEventDispatcher.openBufferScope();
onEntryRemovedInternal(removed, removed.getSbn());
StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey());
if (oldSbn != null) {
updateSuppression(mGroupMap.get(oldSbn.getGroupKey()));
}
mEventDispatcher.closeBufferScope();
}
/**
* An entry was removed.
*
* @param removed the removed entry
* @param sbn the notification the entry has, which doesn't need to be the same as it's internal
* notification
*/
private void onEntryRemovedInternal(NotificationEntry removed,
final StatusBarNotification sbn) {
onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(),
sbn.getNotification().isGroupSummary());
}
private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean
isGroup, boolean isGroupSummary) {
String groupKey = getGroupKey(removed.getKey(), notifGroupKey);
final NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
// When an app posts 2 different notifications as summary of the same group, then a
// cancellation of the first notification removes this group.
// This situation is not supported and we will not allow such notifications anymore in
// the close future. See b/23676310 for reference.
return;
}
if (SPEW) {
Log.d(TAG, "onEntryRemovedInternal: entry=" + logKey(removed)
+ " group=" + logGroupKey(group));
}
if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
group.children.remove(removed.getKey());
} else {
group.summary = null;
}
updateSuppression(group);
if (group.children.isEmpty()) {
if (group.summary == null) {
mGroupMap.remove(groupKey);
mEventDispatcher.notifyGroupRemoved(group);
}
}
}
private void onEntryAddedInternal(final NotificationEntry added) {
if (added.isRowRemoved()) {
added.setDebugThrowable(new Throwable());
}
final StatusBarNotification sbn = added.getSbn();
boolean isGroupChild = isGroupChild(sbn);
String groupKey = getGroupKey(sbn);
NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
group = new NotificationGroup(groupKey);
mGroupMap.put(groupKey, group);
mEventDispatcher.notifyGroupCreated(group);
}
if (SPEW) {
Log.d(TAG, "onEntryAddedInternal: entry=" + logKey(added)
+ " group=" + logGroupKey(group));
}
if (isGroupChild) {
NotificationEntry existing = group.children.get(added.getKey());
if (existing != null && existing != added) {
Throwable existingThrowable = existing.getDebugThrowable();
Log.wtf(TAG, "Inconsistent entries found with the same key " + logKey(added)
+ "existing removed: " + existing.isRowRemoved()
+ (existingThrowable != null
? Log.getStackTraceString(existingThrowable) + "\n" : "")
+ " added removed" + added.isRowRemoved(), new Throwable());
}
group.children.put(added.getKey(), added);
addToPostBatchHistory(group, added);
updateSuppression(group);
} else {
group.summary = added;
addToPostBatchHistory(group, added);
group.expanded = added.areChildrenExpanded();
updateSuppression(group);
if (!group.children.isEmpty()) {
ArrayList<NotificationEntry> childrenCopy =
new ArrayList<>(group.children.values());
for (NotificationEntry child : childrenCopy) {
onEntryBecomingChild(child);
}
mEventDispatcher.notifyGroupsChanged();
}
}
}
private void addToPostBatchHistory(NotificationGroup group, @Nullable NotificationEntry entry) {
if (entry == null) {
return;
}
boolean didAdd = group.postBatchHistory.add(new PostRecord(entry));
if (didAdd) {
trimPostBatchHistory(group.postBatchHistory);
}
}
/** remove all history that's too old to be in the batch. */
private void trimPostBatchHistory(@NonNull TreeSet<PostRecord> postBatchHistory) {
if (postBatchHistory.size() <= 1) {
return;
}
long batchStartTime = postBatchHistory.last().postTime - POST_BATCH_MAX_AGE;
while (!postBatchHistory.isEmpty() && postBatchHistory.first().postTime < batchStartTime) {
postBatchHistory.pollFirst();
}
}
private void onEntryBecomingChild(NotificationEntry entry) {
updateIsolation(entry);
}
private void updateSuppression(NotificationGroup group) {
if (group == null) {
return;
}
NotificationEntry prevAlertOverride = group.alertOverride;
group.alertOverride = getPriorityConversationAlertOverride(group);
int childCount = 0;
boolean hasBubbles = false;
for (NotificationEntry entry : group.children.values()) {
if (mBubblesOptional.isPresent() && mBubblesOptional.get()
.isBubbleNotificationSuppressedFromShade(
entry.getKey(), entry.getSbn().getGroupKey())) {
hasBubbles = true;
} else {
childCount++;
}
}
boolean prevSuppressed = group.suppressed;
group.suppressed = group.summary != null && !group.expanded
&& (childCount == 1
|| (childCount == 0
&& group.summary.getSbn().getNotification().isGroupSummary()
&& (hasIsolatedChildren(group) || hasBubbles)));
boolean alertOverrideChanged = prevAlertOverride != group.alertOverride;
boolean suppressionChanged = prevSuppressed != group.suppressed;
if (alertOverrideChanged || suppressionChanged) {
if (DEBUG) {
Log.d(TAG, "updateSuppression:"
+ " willNotifyListeners=" + !mIsUpdatingUnchangedGroup
+ " changes for group:\n" + group);
if (alertOverrideChanged) {
Log.d(TAG, "updateSuppression: alertOverride was=" + logKey(prevAlertOverride)
+ " now=" + logKey(group.alertOverride));
}
if (suppressionChanged) {
Log.d(TAG, "updateSuppression: suppressed changed to " + group.suppressed);
}
}
if (alertOverrideChanged) {
mEventDispatcher.notifyAlertOverrideChanged(group, prevAlertOverride);
}
if (suppressionChanged) {
mEventDispatcher.notifySuppressedChanged(group);
}
if (!mIsUpdatingUnchangedGroup) {
mEventDispatcher.notifyGroupsChanged();
}
}
}
/**
* Finds the isolated logical child of this group which is should be alerted instead.
*
* Notifications from priority conversations are isolated from their groups to make them more
* prominent, however apps may post these with a GroupAlertBehavior that has the group receiving
* the alert. This would lead to the group alerting even though the conversation that was
* updated was not actually a part of that group. This method finds the best priority
* conversation in this situation, if there is one, so they can be set as the alertOverride of
* the group.
*
* @param group the group to check
* @return the entry which should receive the alert instead of the group, if any.
*/
@Nullable
private NotificationEntry getPriorityConversationAlertOverride(NotificationGroup group) {
// GOAL: if there is a priority child which wouldn't alert based on its groupAlertBehavior,
// but which should be alerting (because priority conversations are isolated), find it.
if (group == null || group.summary == null) {
if (SPEW) {
Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary"
+ " group=" + logGroupKey(group));
}
return null;
}
if (isIsolated(group.summary.getKey())) {
if (SPEW) {
Log.d(TAG, "getPriorityConversationAlertOverride: isolated group"
+ " group=" + logGroupKey(group));
}
return null;
}
// Precondiions:
// * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY
// * Only necessary when at least one notification in the group is on a priority channel
if (group.summary.getSbn().getNotification().getGroupAlertBehavior()
== Notification.GROUP_ALERT_CHILDREN) {
if (SPEW) {
Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN"
+ " group=" + logGroupKey(group));
}
return null;
}
// Get the important children first, copy the keys for the final importance check,
// then add the non-isolated children to the map for unified lookup.
HashMap<String, NotificationEntry> children = getImportantConversations(group);
if (children == null || children.isEmpty()) {
if (SPEW) {
Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations"
+ " group=" + logGroupKey(group));
}
return null;
}
HashSet<String> importantChildKeys = new HashSet<>(children.keySet());
children.putAll(group.children);
// Ensure all children have GROUP_ALERT_SUMMARY
for (NotificationEntry child : children.values()) {
if (child.getSbn().getNotification().getGroupAlertBehavior()
!= Notification.GROUP_ALERT_SUMMARY) {
if (SPEW) {
Log.d(TAG, "getPriorityConversationAlertOverride: child != GROUP_ALERT_SUMMARY"
+ " group=" + logGroupKey(group));
}
return null;
}
}
// Create a merged post history from all the children
TreeSet<PostRecord> combinedHistory = new TreeSet<>(group.postBatchHistory);
for (String importantChildKey : importantChildKeys) {
NotificationGroup importantChildGroup = mGroupMap.get(importantChildKey);
combinedHistory.addAll(importantChildGroup.postBatchHistory);
}
trimPostBatchHistory(combinedHistory);
// This is a streamlined implementation of the following idea:
// * From the subset of notifications in the latest 'batch' of updates. A batch is:
// * Notifs posted less than POST_BATCH_MAX_AGE before the most recently posted.
// * Only including notifs newer than the second-to-last post of any notification.
// * Find the newest child in the batch -- the with the largest 'when' value.
// * If the newest child is a priority conversation, set that as the override.
HashSet<String> batchKeys = new HashSet<>();
long newestChildWhen = -1;
NotificationEntry newestChild = null;
// Iterate backwards through the post history, tracking the child with the smallest sort key
for (PostRecord record : combinedHistory.descendingSet()) {
if (batchKeys.contains(record.key)) {
// Once you see a notification again, the batch has ended
break;
}
batchKeys.add(record.key);
NotificationEntry child = children.get(record.key);
if (child != null) {
long childWhen = child.getSbn().getNotification().when;
if (newestChild == null || childWhen > newestChildWhen) {
newestChildWhen = childWhen;
newestChild = child;
}
}
}
if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) {
if (SPEW) {
Log.d(TAG, "getPriorityConversationAlertOverride:"
+ " result=" + logKey(newestChild)
+ " group=" + logGroupKey(group));
}
return newestChild;
}
if (SPEW) {
Log.d(TAG, "getPriorityConversationAlertOverride:"
+ " result=null newestChild=" + logKey(newestChild)
+ " group=" + logGroupKey(group));
}
return null;
}
private boolean hasIsolatedChildren(NotificationGroup group) {
return getNumberOfIsolatedChildren(group.summary.getSbn().getGroupKey()) != 0;
}
private int getNumberOfIsolatedChildren(String groupKey) {
int count = 0;
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
count++;
}
}
return count;
}
@Nullable
private HashMap<String, NotificationEntry> getImportantConversations(NotificationGroup group) {
String groupKey = group.summary.getSbn().getGroupKey();
HashMap<String, NotificationEntry> result = null;
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
if (sbn.getGroupKey().equals(groupKey)) {
NotificationEntry entry = mGroupMap.get(sbn.getKey()).summary;
if (isImportantConversation(entry)) {
if (result == null) {
result = new HashMap<>();
}
result.put(sbn.getKey(), entry);
}
}
}
return result;
}
private void setStatusBarState(int newState) {
if (newState == StatusBarState.KEYGUARD) {
collapseGroups();
}
}
private void collapseGroups() {
// Because notifications can become isolated when the group becomes suppressed it can
// lead to concurrent modifications while looping. We need to make a copy.
ArrayList<NotificationGroup> groupCopy = new ArrayList<>(mGroupMap.values());
int size = groupCopy.size();
for (int i = 0; i < size; i++) {
NotificationGroup group = groupCopy.get(i);
if (group.expanded) {
setGroupExpanded(group, false);
}
updateSuppression(group);
}
}
public boolean isChildInGroup(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
if (!isGroupChild(sbn)) {
return false;
}
NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null || group.summary == null || group.suppressed) {
return false;
}
if (group.children.isEmpty()) {
// If the suppression of a group changes because the last child was removed, this can
// still be called temporarily because the child hasn't been fully removed yet. Let's
// make sure we still return false in that case.
return false;
}
return true;
}
/**
* If there is a {@link NotificationGroup} associated with the provided entry, this method
* will update the suppression of that group.
*/
public void updateSuppression(NotificationEntry entry) {
NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
if (group != null) {
updateSuppression(group);
}
}
/**
* Get the group key. May differ from the one in the notification due to the notification
* being temporarily isolated.
*
* @param sbn notification to check
* @return the key of the notification
*/
private String getGroupKey(StatusBarNotification sbn) {
return getGroupKey(sbn.getKey(), sbn.getGroupKey());
}
private String getGroupKey(String key, String groupKey) {
if (isIsolated(key)) {
return key;
}
return groupKey;
}
private boolean isIsolated(String sbnKey) {
return mIsolatedEntries.containsKey(sbnKey);
}
/**
* Whether a notification is visually a group child.
*
* @param sbn notification to check
* @return true if it is visually a group child
*/
private boolean isGroupChild(StatusBarNotification sbn) {
return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
}
private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) {
if (isIsolated(key)) {
return false;
}
return isGroup && !isGroupSummary;
}
/**
* Whether a notification that is normally part of a group should be temporarily isolated from
* the group and put in their own group visually. This generally happens when the notification
* is alerting.
*
* @param entry the notification to check
* @return true if the entry should be isolated
*/
private boolean shouldIsolate(NotificationEntry entry) {
StatusBarNotification sbn = entry.getSbn();
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
return false;
}
if (isImportantConversation(entry)) {
return true;
}
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
return (sbn.getNotification().fullScreenIntent != null
|| notificationGroup == null
|| !notificationGroup.expanded
|| isGroupNotFullyVisible(notificationGroup));
}
private boolean isImportantConversation(NotificationEntry entry) {
int peopleNotificationType =
mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
return peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON;
}
/**
* Isolate a notification from its group so that it visually shows as its own group.
*
* @param entry the notification to isolate
*/
private void isolateNotification(NotificationEntry entry) {
if (SPEW) {
Log.d(TAG, "isolateNotification: entry=" + logKey(entry));
}
// We will be isolated now, so lets update the groups
onEntryRemovedInternal(entry, entry.getSbn());
mIsolatedEntries.put(entry.getKey(), entry.getSbn());
onEntryAddedInternal(entry);
// We also need to update the suppression of the old group, because this call comes
// even before the groupManager knows about the notification at all.
// When the notification gets added afterwards it is already isolated and therefore
// it doesn't lead to an update.
updateSuppression(mGroupMap.get(entry.getSbn().getGroupKey()));
mEventDispatcher.notifyGroupsChanged();
}
/**
* Update the isolation of an entry, splitting it from the group.
*/
private void updateIsolation(NotificationEntry entry) {
// We need to buffer a few events because we do isolation changes in 3 steps:
// removeInternal, update mIsolatedEntries, addInternal. This means that often the
// alertOverride will update on the removal, however processing the event in that case can
// cause problems because the mIsolatedEntries map is not in its final state, so the event
// listener may be unable to correctly determine the true state of the group. By delaying
// the alertOverride change until after the add phase, we can ensure that listeners only
// have to handle a consistent state.
mEventDispatcher.openBufferScope();
boolean isIsolated = isIsolated(entry.getSbn().getKey());
if (shouldIsolate(entry)) {
if (!isIsolated) {
isolateNotification(entry);
}
} else if (isIsolated) {
stopIsolatingNotification(entry);
}
mEventDispatcher.closeBufferScope();
}
/**
* Stop isolating a notification and re-group it with its original logical group.
*
* @param entry the notification to un-isolate
*/
private void stopIsolatingNotification(NotificationEntry entry) {
if (SPEW) {
Log.d(TAG, "stopIsolatingNotification: entry=" + logKey(entry));
}
// not isolated anymore, we need to update the groups
onEntryRemovedInternal(entry, entry.getSbn());
mIsolatedEntries.remove(entry.getKey());
onEntryAddedInternal(entry);
mEventDispatcher.notifyGroupsChanged();
}
private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) {
return notificationGroup.summary == null
|| notificationGroup.summary.isGroupNotFullyVisible();
}
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("GroupManagerLegacy state:");
pw.println(" number of groups: " + mGroupMap.size());
for (Map.Entry<String, NotificationGroup> entry : mGroupMap.entrySet()) {
pw.println("\n key: " + logKey(entry.getKey())); pw.println(entry.getValue());
}
pw.println("\n isolated entries: " + mIsolatedEntries.size());
for (Map.Entry<String, StatusBarNotification> entry : mIsolatedEntries.entrySet()) {
pw.print(" "); pw.print(logKey(entry.getKey()));
pw.print(", "); pw.println(entry.getValue());
}
}
@Override
public void onStateChanged(int newState) {
setStatusBarState(newState);
}
/** Get the group key, reformatted for logging, for the (optional) group */
private static String logGroupKey(NotificationGroup group) {
if (group == null) {
return "null";
}
return logKey(group.groupKey);
}
/**
* A record of a notification being posted, containing the time of the post and the key of the
* notification entry. These are stored in a TreeSet by the NotificationGroup and used to
* calculate a batch of notifications.
*/
public static class PostRecord implements Comparable<PostRecord> {
public final long postTime;
public final String key;
/** constructs a record containing the post time and key from the notification entry */
public PostRecord(@NonNull NotificationEntry entry) {
this.postTime = entry.getSbn().getPostTime();
this.key = entry.getKey();
}
@Override
public int compareTo(PostRecord o) {
int postTimeComparison = Long.compare(this.postTime, o.postTime);
return postTimeComparison == 0
? String.CASE_INSENSITIVE_ORDER.compare(this.key, o.key)
: postTimeComparison;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PostRecord that = (PostRecord) o;
return postTime == that.postTime && key.equals(that.key);
}
@Override
public int hashCode() {
return Objects.hash(postTime, key);
}
}
/**
* Represents a notification group in the notification shade.
*/
public static class NotificationGroup {
public final String groupKey;
public final HashMap<String, NotificationEntry> children = new HashMap<>();
public final TreeSet<PostRecord> postBatchHistory = new TreeSet<>();
public NotificationEntry summary;
public boolean expanded;
/**
* Is this notification group suppressed, i.e its summary is hidden
*/
public boolean suppressed;
/**
* The child (which is isolated from this group) to which the alert should be transferred,
* due to priority conversations.
*/
public NotificationEntry alertOverride;
NotificationGroup(String groupKey) {
this.groupKey = groupKey;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(" groupKey: ").append(groupKey);
sb.append("\n summary:");
appendEntry(sb, summary);
sb.append("\n children size: ").append(children.size());
for (NotificationEntry child : children.values()) {
appendEntry(sb, child);
}
sb.append("\n alertOverride:");
appendEntry(sb, alertOverride);
sb.append("\n summary suppressed: ").append(suppressed);
return sb.toString();
}
private void appendEntry(StringBuilder sb, NotificationEntry entry) {
sb.append("\n ").append(entry != null ? entry.getSbn() : "null");
if (entry != null && entry.getDebugThrowable() != null) {
sb.append(Log.getStackTraceString(entry.getDebugThrowable()));
}
}
}
/**
* This class is a toggleable buffer for a subset of events of {@link OnGroupChangeListener}.
* When buffering, instead of notifying the listeners it will set internal state that will allow
* it to notify listeners of those events later
*/
static class GroupEventDispatcher {
private final Function<String, NotificationGroup> mGroupMapGetter;
private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>();
private final HashMap<String, Boolean> mOldSuppressedByGroup = new HashMap<>();
private int mBufferScopeDepth = 0;
private boolean mDidGroupsChange = false;
GroupEventDispatcher(Function<String, NotificationGroup> groupMapGetter) {
mGroupMapGetter = requireNonNull(groupMapGetter);
}
void registerGroupChangeListener(OnGroupChangeListener listener) {
mGroupChangeListeners.add(listener);
}
private boolean isBuffering() {
return mBufferScopeDepth > 0;
}
void notifyAlertOverrideChanged(NotificationGroup group,
NotificationEntry oldAlertOverride) {
if (isBuffering()) {
// The value in this map is the override before the event. If there is an entry
// already in the map, then we are effectively coalescing two events, which means
// we need to preserve the original initial value.
if (!mOldAlertOverrideByGroup.containsKey(group.groupKey)) {
mOldAlertOverrideByGroup.put(group.groupKey, oldAlertOverride);
}
} else {
for (OnGroupChangeListener listener : mGroupChangeListeners) {
listener.onGroupAlertOverrideChanged(group, oldAlertOverride,
group.alertOverride);
}
}
}
void notifySuppressedChanged(NotificationGroup group) {
if (isBuffering()) {
// The value in this map is the 'suppressed' before the event. If there is a value
// already in the map, then we are effectively coalescing two events, which means
// we need to preserve the original initial value.
mOldSuppressedByGroup.putIfAbsent(group.groupKey, !group.suppressed);
} else {
for (OnGroupChangeListener listener : mGroupChangeListeners) {
listener.onGroupSuppressionChanged(group, group.suppressed);
}
}
}
void notifyGroupsChanged() {
if (isBuffering()) {
mDidGroupsChange = true;
} else {
for (OnGroupChangeListener listener : mGroupChangeListeners) {
listener.onGroupsChanged();
}
}
}
void notifyGroupCreated(NotificationGroup group) {
// NOTE: given how this event is used, it doesn't need to be buffered right now
final String groupKey = group.groupKey;
for (OnGroupChangeListener listener : mGroupChangeListeners) {
listener.onGroupCreated(group, groupKey);
}
}
void notifyGroupRemoved(NotificationGroup group) {
// NOTE: given how this event is used, it doesn't need to be buffered right now
final String groupKey = group.groupKey;
for (OnGroupChangeListener listener : mGroupChangeListeners) {
listener.onGroupRemoved(group, groupKey);
}
}
void openBufferScope() {
mBufferScopeDepth++;
if (SPEW) {
Log.d(TAG, "openBufferScope: scopeDepth=" + mBufferScopeDepth);
}
}
void closeBufferScope() {
mBufferScopeDepth--;
if (SPEW) {
Log.d(TAG, "closeBufferScope: scopeDepth=" + mBufferScopeDepth);
}
// Flush buffered events if the last buffer scope has closed
if (!isBuffering()) {
flushBuffer();
}
}
private void flushBuffer() {
if (SPEW) {
Log.d(TAG, "flushBuffer: "
+ " suppressed.size=" + mOldSuppressedByGroup.size()
+ " alertOverride.size=" + mOldAlertOverrideByGroup.size()
+ " mDidGroupsChange=" + mDidGroupsChange);
}
// alert all group suppressed changes for groups that were not removed
for (Map.Entry<String, Boolean> entry : mOldSuppressedByGroup.entrySet()) {
NotificationGroup group = mGroupMapGetter.apply(entry.getKey());
if (group == null) {
// The group can be null if this suppressed changed before the group was
// permanently removed, meaning that there's no guarantee that listeners will
// that field clear.
if (SPEW) {
Log.d(TAG, "flushBuffer: suppressed:"
+ " cannot report for removed group: " + logKey(entry.getKey()));
}
continue;
}
boolean oldSuppressed = entry.getValue();
if (group.suppressed == oldSuppressed) {
// If the final suppressed equals the initial, it means we coalesced two
// events which undid the change, so we can drop it entirely.
if (SPEW) {
Log.d(TAG, "flushBuffer: suppressed:"
+ " did not change for group: " + logKey(entry.getKey()));
}
continue;
}
notifySuppressedChanged(group);
}
mOldSuppressedByGroup.clear();
// alert all group alert override changes for groups that were not removed
for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) {
NotificationGroup group = mGroupMapGetter.apply(entry.getKey());
if (group == null) {
// The group can be null if this alertOverride changed before the group was
// permanently removed, meaning that there's no guarantee that listeners will
// that field clear.
if (SPEW) {
Log.d(TAG, "flushBuffer: alertOverride:"
+ " cannot report for removed group: " + entry.getKey());
}
continue;
}
NotificationEntry oldAlertOverride = entry.getValue();
if (group.alertOverride == oldAlertOverride) {
// If the final alertOverride equals the initial, it means we coalesced two
// events which undid the change, so we can drop it entirely.
if (SPEW) {
Log.d(TAG, "flushBuffer: alertOverride:"
+ " did not change for group: " + logKey(entry.getKey()));
}
continue;
}
notifyAlertOverrideChanged(group, oldAlertOverride);
}
mOldAlertOverrideByGroup.clear();
// alert that groups changed
if (mDidGroupsChange) {
notifyGroupsChanged();
mDidGroupsChange = false;
}
}
}
/**
* Listener for group changes not including group expansion changes which are handled by
* {@link OnGroupExpansionChangeListener}.
*/
public interface OnGroupChangeListener {
/**
* A new group has been created.
*
* @param group the group that was created
* @param groupKey the group's key
*/
default void onGroupCreated(
NotificationGroup group,
String groupKey) {}
/**
* A group has been removed.
*
* @param group the group that was removed
* @param groupKey the group's key
*/
default void onGroupRemoved(
NotificationGroup group,
String groupKey) {}
/**
* The suppression of a group has changed.
*
* @param group the group that has changed
* @param suppressed true if the group is now suppressed, false o/w
*/
default void onGroupSuppressionChanged(
NotificationGroup group,
boolean suppressed) {}
/**
* The alert override of a group has changed.
*
* @param group the group that has changed
* @param oldAlertOverride the previous notification to which the group's alerts were sent
* @param newAlertOverride the notification to which the group's alerts should now be sent
*/
default void onGroupAlertOverrideChanged(
NotificationGroup group,
@Nullable NotificationEntry oldAlertOverride,
@Nullable NotificationEntry newAlertOverride) {}
/**
* The groups have changed. This can happen if the isolation of a child has changes or if a
* group became suppressed / unsuppressed
*/
default void onGroupsChanged() {}
}
}

View File

@@ -24,7 +24,6 @@ import android.os.Handler;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -37,9 +36,7 @@ import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.NotifPanelEventsModule;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
@@ -53,7 +50,6 @@ import com.android.systemui.statusbar.notification.collection.inflation.BindEven
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -84,7 +80,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationSectionsMan
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.wmshell.BubblesManager;
import java.util.Optional;
@@ -118,16 +113,8 @@ public interface NotificationsModule {
@SysUISingleton
@Provides
static NotificationEntryManager provideNotificationEntryManager(
NotificationEntryManagerLogger logger,
NotificationGroupManagerLegacy groupManager,
NotifPipelineFlags notifPipelineFlags,
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
@Background Executor bgExecutor) {
return new NotificationEntryManager(
logger
);
NotificationEntryManagerLogger logger) {
return new NotificationEntryManager(logger);
}
/** Provides an instance of {@link NotificationGutsManager} */
@@ -141,7 +128,6 @@ public interface NotificationsModule {
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
NotificationEntryManager notificationEntryManager,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
ShortcutManager shortcutManager,

View File

@@ -87,8 +87,6 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -633,7 +631,6 @@ public class NotificationStackScrollLayoutController {
NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
CentralSurfaces centralSurfaces,
ScrimController scrimController,
NotificationGroupManagerLegacy legacyGroupManager,
GroupExpansionManager groupManager,
@SilentHeader SectionHeaderController silentHeaderController,
NotifPipeline notifPipeline,
@@ -677,12 +674,6 @@ public class NotificationStackScrollLayoutController {
mJankMonitor = jankMonitor;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
mGroupExpansionManager = groupManager;
legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() {
@Override
public void onGroupsChanged() {
mCentralSurfaces.requestNotificationUpdate("onGroupsChanged");
}
});
mSilentHeaderController = silentHeaderController;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;

View File

@@ -1,294 +0,0 @@
/*
* 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.systemui.statusbar.notification.collection.legacy
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.GroupEventDispatcher
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener
import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class GroupEventDispatcherTest : SysuiTestCase() {
val groupMap = mutableMapOf<String, NotificationGroup>()
val groupTestHelper = NotificationGroupTestHelper(mContext)
private val dispatcher = GroupEventDispatcher(groupMap::get)
private val listener: OnGroupChangeListener = mock()
@Before
fun setup() {
dispatcher.registerGroupChangeListener(listener)
}
@Test
fun testOnGroupsChangedUnbuffered() {
dispatcher.notifyGroupsChanged()
verify(listener).onGroupsChanged()
verifyNoMoreInteractions(listener)
}
@Test
fun testOnGroupsChangedBuffered() {
dispatcher.openBufferScope()
dispatcher.notifyGroupsChanged()
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verify(listener).onGroupsChanged()
verifyNoMoreInteractions(listener)
}
@Test
fun testOnGroupsChangedDoubleBuffered() {
dispatcher.openBufferScope()
dispatcher.notifyGroupsChanged()
dispatcher.openBufferScope() // open a nested buffer scope
dispatcher.notifyGroupsChanged()
dispatcher.closeBufferScope() // should NOT flush events
dispatcher.notifyGroupsChanged()
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope() // this SHOULD flush events
verify(listener).onGroupsChanged()
verifyNoMoreInteractions(listener)
}
@Test
fun testOnGroupsChangedBufferCoalesces() {
dispatcher.openBufferScope()
dispatcher.notifyGroupsChanged()
dispatcher.notifyGroupsChanged()
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verify(listener).onGroupsChanged()
verifyNoMoreInteractions(listener)
}
@Test
fun testOnGroupCreatedIsNeverBuffered() {
val group = addGroup(1)
dispatcher.openBufferScope()
dispatcher.notifyGroupCreated(group)
verify(listener).onGroupCreated(group, group.groupKey)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verifyNoMoreInteractions(listener)
}
@Test
fun testOnGroupRemovedIsNeverBuffered() {
val group = addGroup(1)
dispatcher.openBufferScope()
dispatcher.notifyGroupRemoved(group)
verify(listener).onGroupRemoved(group, group.groupKey)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideAddedUnbuffered() {
val group = addGroup(1)
val newAlertEntry = groupTestHelper.createChildNotification()
group.alertOverride = newAlertEntry
dispatcher.notifyAlertOverrideChanged(group, null)
verify(listener).onGroupAlertOverrideChanged(group, null, newAlertEntry)
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideRemovedUnbuffered() {
val group = addGroup(1)
val oldAlertEntry = groupTestHelper.createChildNotification()
dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, null)
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideChangedUnbuffered() {
val group = addGroup(1)
val oldAlertEntry = groupTestHelper.createChildNotification()
val newAlertEntry = groupTestHelper.createChildNotification()
group.alertOverride = newAlertEntry
dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry)
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideChangedBuffered() {
dispatcher.openBufferScope()
val group = addGroup(1)
val oldAlertEntry = groupTestHelper.createChildNotification()
val newAlertEntry = groupTestHelper.createChildNotification()
group.alertOverride = newAlertEntry
dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry)
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideIgnoredIfRemoved() {
dispatcher.openBufferScope()
val group = addGroup(1)
val oldAlertEntry = groupTestHelper.createChildNotification()
val newAlertEntry = groupTestHelper.createChildNotification()
group.alertOverride = newAlertEntry
dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
verifyNoMoreInteractions(listener)
groupMap.clear()
dispatcher.closeBufferScope()
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideMultipleChangesBuffered() {
dispatcher.openBufferScope()
val group = addGroup(1)
val oldAlertEntry = groupTestHelper.createChildNotification()
val newAlertEntry = groupTestHelper.createChildNotification()
group.alertOverride = null
dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
group.alertOverride = newAlertEntry
dispatcher.notifyAlertOverrideChanged(group, null)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry)
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideTemporaryValueSwallowed() {
dispatcher.openBufferScope()
val group = addGroup(1)
val stableAlertEntry = groupTestHelper.createChildNotification()
group.alertOverride = null
dispatcher.notifyAlertOverrideChanged(group, stableAlertEntry)
group.alertOverride = stableAlertEntry
dispatcher.notifyAlertOverrideChanged(group, null)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verifyNoMoreInteractions(listener)
}
@Test
fun testAlertOverrideTemporaryNullSwallowed() {
dispatcher.openBufferScope()
val group = addGroup(1)
val temporaryAlertEntry = groupTestHelper.createChildNotification()
group.alertOverride = temporaryAlertEntry
dispatcher.notifyAlertOverrideChanged(group, null)
group.alertOverride = null
dispatcher.notifyAlertOverrideChanged(group, temporaryAlertEntry)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verifyNoMoreInteractions(listener)
}
@Test
fun testSuppressOnUnbuffered() {
val group = addGroup(1)
group.suppressed = true
dispatcher.notifySuppressedChanged(group)
verify(listener).onGroupSuppressionChanged(group, true)
verifyNoMoreInteractions(listener)
}
@Test
fun testSuppressOffUnbuffered() {
val group = addGroup(1)
group.suppressed = false
dispatcher.notifySuppressedChanged(group)
verify(listener).onGroupSuppressionChanged(group, false)
verifyNoMoreInteractions(listener)
}
@Test
fun testSuppressOnBuffered() {
dispatcher.openBufferScope()
val group = addGroup(1)
group.suppressed = false
dispatcher.notifySuppressedChanged(group)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verify(listener).onGroupSuppressionChanged(group, false)
verifyNoMoreInteractions(listener)
}
@Test
fun testSuppressOnIgnoredIfRemoved() {
dispatcher.openBufferScope()
val group = addGroup(1)
group.suppressed = false
dispatcher.notifySuppressedChanged(group)
verifyNoMoreInteractions(listener)
groupMap.clear()
dispatcher.closeBufferScope()
verifyNoMoreInteractions(listener)
}
@Test
fun testSuppressOnOffBuffered() {
dispatcher.openBufferScope()
val group = addGroup(1)
group.suppressed = true
dispatcher.notifySuppressedChanged(group)
group.suppressed = false
dispatcher.notifySuppressedChanged(group)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verifyNoMoreInteractions(listener)
}
@Test
fun testSuppressOnOffOnBuffered() {
dispatcher.openBufferScope()
val group = addGroup(1)
group.suppressed = true
dispatcher.notifySuppressedChanged(group)
group.suppressed = false
dispatcher.notifySuppressedChanged(group)
group.suppressed = true
dispatcher.notifySuppressedChanged(group)
verifyNoMoreInteractions(listener)
dispatcher.closeBufferScope()
verify(listener).onGroupSuppressionChanged(group, true)
verifyNoMoreInteractions(listener)
}
private fun addGroup(id: Int): NotificationGroup {
val group = NotificationGroup("group:$id")
groupMap[group.groupKey] = group
return group
}
}

View File

@@ -34,7 +34,6 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.metrics.LogMaker;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import androidx.test.filters.SmallTest;
@@ -42,11 +41,9 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -61,12 +58,9 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -112,7 +106,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private SysuiColorExtractor mColorExtractor;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private MetricsLogger mMetricsLogger;
@Mock private DumpManager mDumpManager;
@@ -122,19 +115,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationSwipeHelper mNotificationSwipeHelper;
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ScrimController mScrimController;
@Mock private NotificationGroupManagerLegacy mGroupManagerLegacy;
@Mock private GroupExpansionManager mGroupExpansionManager;
@Mock private SectionHeaderController mSilentHeaderController;
@Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NotifCollection mNotifCollection;
@Mock private NotificationEntryManager mEntryManager;
@Mock private IStatusBarService mIStatusBarService;
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock private LayoutInflater mLayoutInflater;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private VisualStabilityManager mVisualStabilityManager;
@Mock private ShadeController mShadeController;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private StackStateLogger mStackLogger;
@@ -176,7 +164,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mNotificationSwipeHelperBuilder,
mCentralSurfaces,
mScrimController,
mGroupManagerLegacy,
mGroupExpansionManager,
mSilentHeaderController,
mNotifPipeline,