Merge "Isolating important conversations" into rvc-dev am: 822b72843d
Change-Id: I856f21fbb6a570f381957509742ba3b22a14d5ed
This commit is contained in:
@@ -177,12 +177,32 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
|
||||
currentUserId);
|
||||
ent.setSensitive(sensitive, deviceSensitive);
|
||||
ent.getRow().setNeedsRedaction(needsRedaction);
|
||||
if (mGroupManager.isChildInGroupWithSummary(ent.getSbn())) {
|
||||
NotificationEntry summary = mGroupManager.getGroupSummary(ent.getSbn());
|
||||
List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(summary);
|
||||
boolean isChildInGroup = mGroupManager.isChildInGroupWithSummary(ent.getSbn());
|
||||
|
||||
boolean groupChangesAllowed = mVisualStabilityManager.areGroupChangesAllowed()
|
||||
|| !ent.hasFinishedInitialization();
|
||||
NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn());
|
||||
if (!groupChangesAllowed) {
|
||||
// We don't to change groups while the user is looking at them
|
||||
boolean wasChildInGroup = ent.isChildInGroup();
|
||||
if (isChildInGroup && !wasChildInGroup) {
|
||||
isChildInGroup = wasChildInGroup;
|
||||
mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
|
||||
} else if (!isChildInGroup && wasChildInGroup) {
|
||||
// We allow grouping changes if the group was collapsed
|
||||
if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
|
||||
isChildInGroup = wasChildInGroup;
|
||||
parent = ent.getRow().getNotificationParent().getEntry();
|
||||
mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isChildInGroup) {
|
||||
List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent);
|
||||
if (orderedChildren == null) {
|
||||
orderedChildren = new ArrayList<>();
|
||||
mTmpChildOrderMap.put(summary, orderedChildren);
|
||||
mTmpChildOrderMap.put(parent, orderedChildren);
|
||||
}
|
||||
orderedChildren.add(ent);
|
||||
} else {
|
||||
@@ -205,7 +225,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
|
||||
}
|
||||
|
||||
for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
|
||||
if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getEntry().getSbn())) {
|
||||
if (mEntryManager.getPendingOrActiveNotif(viewToRemove.getEntry().getKey()) != null) {
|
||||
// we are only transferring this notification to its parent, don't generate an
|
||||
// animation
|
||||
mListContainer.setChildTransferInProgress(true);
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.android.internal.widget.ConversationLayout
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||
import com.android.systemui.statusbar.notification.row.NotificationContentView
|
||||
import com.android.systemui.statusbar.phone.NotificationGroupManager
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -60,6 +61,7 @@ class ConversationNotificationProcessor @Inject constructor(
|
||||
@Singleton
|
||||
class ConversationNotificationManager @Inject constructor(
|
||||
private val notificationEntryManager: NotificationEntryManager,
|
||||
private val notificationGroupManager: NotificationGroupManager,
|
||||
private val context: Context
|
||||
) {
|
||||
// Need this state to be thread safe, since it's accessed from the ui thread
|
||||
@@ -81,10 +83,19 @@ class ConversationNotificationManager @Inject constructor(
|
||||
if (rankingMap.getRanking(entry.sbn.key, ranking) &&
|
||||
ranking.isConversation) {
|
||||
val important = ranking.channel.isImportantConversation
|
||||
var changed = false
|
||||
entry.row?.layouts?.asSequence()
|
||||
?.flatMap(::getLayouts)
|
||||
?.mapNotNull { it as? ConversationLayout }
|
||||
?.forEach { it.setIsImportantConversation(important) }
|
||||
?.forEach {
|
||||
if (important != it.isImportantConversation) {
|
||||
it.setIsImportantConversation(important)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
notificationGroupManager.updateIsolation(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReorderingAllowed() {
|
||||
public void onChangeAllowed() {
|
||||
updateNotifications("reordering is now allowed");
|
||||
}
|
||||
|
||||
|
||||
@@ -42,12 +42,14 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
|
||||
private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
|
||||
|
||||
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
|
||||
private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
|
||||
private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
|
||||
private final Handler mHandler;
|
||||
|
||||
private boolean mPanelExpanded;
|
||||
private boolean mScreenOn;
|
||||
private boolean mReorderingAllowed;
|
||||
private boolean mGroupChangedAllowed;
|
||||
private boolean mIsTemporaryReorderingAllowed;
|
||||
private long mTemporaryReorderingStart;
|
||||
private VisibilityLocationProvider mVisibilityLocationProvider;
|
||||
@@ -83,13 +85,22 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
|
||||
/**
|
||||
* Add a callback to invoke when reordering is allowed again.
|
||||
* @param callback
|
||||
*/
|
||||
public void addReorderingAllowedCallback(Callback callback) {
|
||||
if (mCallbacks.contains(callback)) {
|
||||
if (mReorderingAllowedCallbacks.contains(callback)) {
|
||||
return;
|
||||
}
|
||||
mCallbacks.add(callback);
|
||||
mReorderingAllowedCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to invoke when group changes are allowed again.
|
||||
*/
|
||||
public void addGroupChangesAllowedCallback(Callback callback) {
|
||||
if (mGroupChangesAllowedCallbacks.contains(callback)) {
|
||||
return;
|
||||
}
|
||||
mGroupChangesAllowedCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +108,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
*/
|
||||
public void setPanelExpanded(boolean expanded) {
|
||||
mPanelExpanded = expanded;
|
||||
updateReorderingAllowed();
|
||||
updateAllowedStates();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +116,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
*/
|
||||
public void setScreenOn(boolean screenOn) {
|
||||
mScreenOn = screenOn;
|
||||
updateReorderingAllowed();
|
||||
updateAllowedStates();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,25 +127,30 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
return;
|
||||
}
|
||||
mPulsing = pulsing;
|
||||
updateReorderingAllowed();
|
||||
updateAllowedStates();
|
||||
}
|
||||
|
||||
private void updateReorderingAllowed() {
|
||||
private void updateAllowedStates() {
|
||||
boolean reorderingAllowed =
|
||||
(!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
|
||||
boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
|
||||
mReorderingAllowed = reorderingAllowed;
|
||||
if (changedToTrue) {
|
||||
notifyCallbacks();
|
||||
notifyChangeAllowed(mReorderingAllowedCallbacks);
|
||||
}
|
||||
boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
|
||||
changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
|
||||
mGroupChangedAllowed = groupChangesAllowed;
|
||||
if (changedToTrue) {
|
||||
notifyChangeAllowed(mGroupChangesAllowedCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCallbacks() {
|
||||
for (int i = 0; i < mCallbacks.size(); i++) {
|
||||
Callback callback = mCallbacks.get(i);
|
||||
callback.onReorderingAllowed();
|
||||
private void notifyChangeAllowed(ArrayList<Callback> callbacks) {
|
||||
for (int i = 0; i < callbacks.size(); i++) {
|
||||
callbacks.get(i).onChangeAllowed();
|
||||
}
|
||||
mCallbacks.clear();
|
||||
callbacks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,6 +160,13 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
return mReorderingAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether changes in the grouping should be allowed right now.
|
||||
*/
|
||||
public boolean areGroupChangesAllowed() {
|
||||
return mGroupChangedAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether a specific notification is allowed to reorder. Certain notifications are
|
||||
* allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added
|
||||
@@ -197,12 +220,12 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
mTemporaryReorderingStart = SystemClock.elapsedRealtime();
|
||||
}
|
||||
mIsTemporaryReorderingAllowed = true;
|
||||
updateReorderingAllowed();
|
||||
updateAllowedStates();
|
||||
}
|
||||
|
||||
private final Runnable mOnTemporaryReorderingExpired = () -> {
|
||||
mIsTemporaryReorderingAllowed = false;
|
||||
updateReorderingAllowed();
|
||||
updateAllowedStates();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -229,9 +252,9 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
|
||||
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called when reordering is allowed again.
|
||||
* Called when changing is allowed again.
|
||||
*/
|
||||
void onReorderingAllowed();
|
||||
void onChangeAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ public final class NotificationEntry extends ListEntry {
|
||||
*/
|
||||
public EditedSuggestionInfo editedSuggestionInfo;
|
||||
|
||||
private NotificationEntry parent; // our parent (if we're in a group)
|
||||
private ExpandableNotificationRow row; // the outer expanded view
|
||||
private ExpandableNotificationRowController mRowController;
|
||||
|
||||
@@ -710,7 +709,7 @@ public final class NotificationEntry extends ListEntry {
|
||||
}
|
||||
|
||||
public boolean isChildInGroup() {
|
||||
return parent == null;
|
||||
return row != null && row.isChildInGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.coordinator
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* A coordinator that elevates important conversation notifications
|
||||
*/
|
||||
@Singleton
|
||||
class ConversationCoordinator @Inject constructor() : Coordinator {
|
||||
|
||||
private val notificationPromoter = object : NotifPromoter(TAG) {
|
||||
override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean {
|
||||
return entry.channel?.isImportantConversation == true
|
||||
}
|
||||
}
|
||||
|
||||
override fun attach(pipeline: NotifPipeline) {
|
||||
pipeline.addPromoter(notificationPromoter)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ConversationCoordinator"
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public class NotifCoordinators implements Dumpable {
|
||||
DeviceProvisionedCoordinator deviceProvisionedCoordinator,
|
||||
BubbleCoordinator bubbleCoordinator,
|
||||
HeadsUpCoordinator headsUpCoordinator,
|
||||
ConversationCoordinator conversationCoordinator,
|
||||
PreparationCoordinator preparationCoordinator) {
|
||||
dumpManager.registerDumpable(TAG, this);
|
||||
mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
|
||||
@@ -66,6 +67,7 @@ public class NotifCoordinators implements Dumpable {
|
||||
mCoordinators.add(deviceProvisionedCoordinator);
|
||||
mCoordinators.add(bubbleCoordinator);
|
||||
if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
|
||||
mCoordinators.add(conversationCoordinator);
|
||||
mCoordinators.add(headsUpCoordinator);
|
||||
mCoordinators.add(preparationCoordinator);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class PeopleNotificationIdentifierImpl @Inject constructor(
|
||||
return TYPE_NON_PERSON
|
||||
}
|
||||
|
||||
val childTypes = groupManager.getLogicalChildren(statusBarNotification)
|
||||
val childTypes = groupManager.getChildren(statusBarNotification)
|
||||
?.asSequence()
|
||||
?.map { getPeopleNotificationType(it.sbn, it.ranking) }
|
||||
?: return TYPE_NON_PERSON
|
||||
|
||||
@@ -20,7 +20,6 @@ import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
|
||||
import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS;
|
||||
|
||||
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
|
||||
|
||||
@@ -44,7 +43,6 @@ import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
import android.transition.ChangeBounds;
|
||||
@@ -56,15 +54,12 @@ import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settingslib.notification.ConversationIconFactory;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
@@ -517,7 +512,6 @@ public class NotificationConversationInfo extends LinearLayout implements
|
||||
bgHandler.post(
|
||||
new UpdateChannelRunnable(mINotificationManager, mPackageName,
|
||||
mAppUid, mSelectedAction, mNotificationChannel));
|
||||
mVisualStabilityManager.temporarilyAllowReordering();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -337,7 +337,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
|
||||
// VisualStabilityManager.Callback overrides:
|
||||
|
||||
@Override
|
||||
public void onReorderingAllowed() {
|
||||
public void onChangeAllowed() {
|
||||
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
|
||||
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
|
||||
if (isAlerting(entry.getKey())) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.NotificationChannel;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
@@ -85,6 +86,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
return group.expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the group that this notification is associated with logically is expanded
|
||||
*/
|
||||
public boolean isLogicalGroupExpanded(StatusBarNotification sbn) {
|
||||
NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
|
||||
if (group == null) {
|
||||
return false;
|
||||
}
|
||||
return group.expanded;
|
||||
}
|
||||
|
||||
public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
|
||||
NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
|
||||
if (group == null) {
|
||||
@@ -147,7 +159,15 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the group manager that a new entry was added
|
||||
*/
|
||||
public void onEntryAdded(final NotificationEntry added) {
|
||||
updateIsolation(added);
|
||||
onEntryAddedInternal(added);
|
||||
}
|
||||
|
||||
private void onEntryAddedInternal(final NotificationEntry added) {
|
||||
if (added.isRowRemoved()) {
|
||||
added.setDebugThrowable(new Throwable());
|
||||
}
|
||||
@@ -193,9 +213,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
}
|
||||
|
||||
private void onEntryBecomingChild(NotificationEntry entry) {
|
||||
if (shouldIsolate(entry)) {
|
||||
isolateNotification(entry);
|
||||
}
|
||||
updateIsolation(entry);
|
||||
}
|
||||
|
||||
private void updateSuppression(NotificationGroup group) {
|
||||
@@ -242,15 +260,6 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
return count;
|
||||
}
|
||||
|
||||
private NotificationEntry getIsolatedChild(String groupKey) {
|
||||
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
|
||||
if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
|
||||
return mGroupMap.get(sbn.getKey()).summary;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an entry's group information
|
||||
* @param entry notification entry to update
|
||||
@@ -278,7 +287,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
|
||||
onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
|
||||
}
|
||||
onEntryAdded(entry);
|
||||
onEntryAddedInternal(entry);
|
||||
mIsUpdatingUnchangedGroup = false;
|
||||
if (isIsolated(entry.getSbn().getKey())) {
|
||||
mIsolatedEntries.put(entry.getKey(), entry.getSbn());
|
||||
@@ -413,13 +422,28 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
return null;
|
||||
}
|
||||
ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values());
|
||||
NotificationEntry isolatedChild = getIsolatedChild(summary.getGroupKey());
|
||||
if (isolatedChild != null) {
|
||||
children.add(isolatedChild);
|
||||
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
|
||||
if (sbn.getGroupKey().equals(summary.getGroupKey())) {
|
||||
children.add(mGroupMap.get(sbn.getKey()).summary);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children that are in the summary's group, not including those isolated.
|
||||
*
|
||||
* @param summary summary of a group
|
||||
* @return list of the children
|
||||
*/
|
||||
public @Nullable ArrayList<NotificationEntry> getChildren(StatusBarNotification summary) {
|
||||
NotificationGroup group = mGroupMap.get(summary.getGroupKey());
|
||||
if (group == null) {
|
||||
return null;
|
||||
}
|
||||
return new ArrayList<>(group.children.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a {@link NotificationGroup} associated with the provided entry, this method
|
||||
* will update the suppression of that group.
|
||||
@@ -495,17 +519,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
|
||||
@Override
|
||||
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
|
||||
onAlertStateChanged(entry, isHeadsUp);
|
||||
}
|
||||
|
||||
private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting) {
|
||||
if (isAlerting) {
|
||||
if (shouldIsolate(entry)) {
|
||||
isolateNotification(entry);
|
||||
}
|
||||
} else {
|
||||
stopIsolatingNotification(entry);
|
||||
}
|
||||
updateIsolation(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,13 +533,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
|
||||
private boolean shouldIsolate(NotificationEntry entry) {
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
|
||||
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
|
||||
return false;
|
||||
}
|
||||
NotificationChannel channel = entry.getChannel();
|
||||
if (channel != null && channel.isImportantConversation()) {
|
||||
return true;
|
||||
}
|
||||
if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
|
||||
return false;
|
||||
}
|
||||
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
|
||||
return (sbn.getNotification().fullScreenIntent != null
|
||||
|| notificationGroup == null
|
||||
|| !notificationGroup.expanded
|
||||
@@ -545,7 +563,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
|
||||
mIsolatedEntries.put(sbn.getKey(), sbn);
|
||||
|
||||
onEntryAdded(entry);
|
||||
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
|
||||
@@ -556,6 +574,20 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the isolation of an entry, splitting it from the group.
|
||||
*/
|
||||
public void updateIsolation(NotificationEntry entry) {
|
||||
boolean isIsolated = isIsolated(entry.getSbn().getKey());
|
||||
if (shouldIsolate(entry)) {
|
||||
if (!isIsolated) {
|
||||
isolateNotification(entry);
|
||||
}
|
||||
} else if (isIsolated) {
|
||||
stopIsolatingNotification(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop isolating a notification and re-group it with its original logical group.
|
||||
*
|
||||
@@ -563,11 +595,11 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
*/
|
||||
private void stopIsolatingNotification(NotificationEntry entry) {
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
if (mIsolatedEntries.containsKey(sbn.getKey())) {
|
||||
if (isIsolated(sbn.getKey())) {
|
||||
// not isolated anymore, we need to update the groups
|
||||
onEntryRemovedInternal(entry, entry.getSbn());
|
||||
mIsolatedEntries.remove(sbn.getKey());
|
||||
onEntryAdded(entry);
|
||||
onEntryAddedInternal(entry);
|
||||
for (OnGroupChangeListener listener : mListeners) {
|
||||
listener.onGroupsChanged();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
|
||||
mLockscreenUserManager);
|
||||
mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
|
||||
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
|
||||
when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
|
||||
when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
|
||||
|
||||
mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase {
|
||||
mVisualStabilityManager.setScreenOn(true);
|
||||
mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
|
||||
mVisualStabilityManager.setScreenOn(false);
|
||||
verify(mCallback).onReorderingAllowed();
|
||||
verify(mCallback).onChangeAllowed();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -119,7 +119,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase {
|
||||
mVisualStabilityManager.setScreenOn(true);
|
||||
mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
|
||||
mVisualStabilityManager.setPanelExpanded(false);
|
||||
verify(mCallback).onReorderingAllowed();
|
||||
verify(mCallback).onChangeAllowed();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -130,7 +130,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase {
|
||||
mVisualStabilityManager.setScreenOn(false);
|
||||
mVisualStabilityManager.setScreenOn(true);
|
||||
mVisualStabilityManager.setScreenOn(false);
|
||||
verify(mCallback).onReorderingAllowed();
|
||||
verify(mCallback).onChangeAllowed();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -190,7 +190,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase {
|
||||
mVisualStabilityManager.setPulsing(true);
|
||||
mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
|
||||
mVisualStabilityManager.setPulsing(false);
|
||||
verify(mCallback).onReorderingAllowed();
|
||||
verify(mCallback).onChangeAllowed();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -204,7 +204,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase {
|
||||
mVisualStabilityManager.temporarilyAllowReordering();
|
||||
|
||||
// THEN callbacks are notified that reordering is allowed
|
||||
verify(mCallback).onReorderingAllowed();
|
||||
verify(mCallback).onChangeAllowed();
|
||||
assertTrue(mVisualStabilityManager.isReorderingAllowed());
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase {
|
||||
mVisualStabilityManager.temporarilyAllowReordering();
|
||||
|
||||
// THEN reordering is still not allowed
|
||||
verify(mCallback, never()).onReorderingAllowed();
|
||||
verify(mCallback, never()).onChangeAllowed();
|
||||
assertFalse(mVisualStabilityManager.isReorderingAllowed());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.coordinator
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class ConversationCoordinatorTest : SysuiTestCase() {
|
||||
|
||||
private var coordinator: ConversationCoordinator = ConversationCoordinator()
|
||||
|
||||
// captured listeners and pluggables:
|
||||
private var promoter: NotifPromoter? = null
|
||||
|
||||
@Mock
|
||||
private val pipeline: NotifPipeline? = null
|
||||
@Mock
|
||||
private val channel: NotificationChannel? = null
|
||||
private var entry: NotificationEntry? = null
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(channel!!.isImportantConversation).thenReturn(true)
|
||||
|
||||
coordinator.attach(pipeline!!)
|
||||
|
||||
// capture arguments:
|
||||
val notifPromoterCaptor = ArgumentCaptor.forClass(NotifPromoter::class.java)
|
||||
verify(pipeline).addPromoter(notifPromoterCaptor.capture())
|
||||
promoter = notifPromoterCaptor.value
|
||||
|
||||
entry = NotificationEntryBuilder().setChannel(channel).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPromotesCurrentHUN() {
|
||||
|
||||
// only promote important conversations
|
||||
assertTrue(promoter!!.shouldPromoteToTopLevel(entry))
|
||||
assertFalse(promoter!!.shouldPromoteToTopLevel(NotificationEntryBuilder().build()))
|
||||
}
|
||||
}
|
||||
@@ -816,29 +816,4 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
|
||||
verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
|
||||
anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdjustImportanceTemporarilyAllowsReordering() {
|
||||
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
|
||||
mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
|
||||
mNotificationInfo.bindNotification(
|
||||
mShortcutManager,
|
||||
mMockPackageManager,
|
||||
mMockINotificationManager,
|
||||
mVisualStabilityManager,
|
||||
TEST_PACKAGE_NAME,
|
||||
mNotificationChannel,
|
||||
mEntry,
|
||||
null,
|
||||
null,
|
||||
mIconFactory,
|
||||
true);
|
||||
|
||||
mNotificationInfo.findViewById(R.id.silence).performClick();
|
||||
mNotificationInfo.findViewById(R.id.done).performClick();
|
||||
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
verify(mVisualStabilityManager).temporarilyAllowReordering();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6227,7 +6227,7 @@ public class NotificationManagerService extends SystemService {
|
||||
cancelNotificationLocked(
|
||||
r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
|
||||
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
|
||||
mSendDelete, childrenFlagChecker);
|
||||
mSendDelete, childrenFlagChecker, mReason);
|
||||
updateLightsLocked();
|
||||
if (mShortcutHelper != null) {
|
||||
mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
|
||||
@@ -6687,7 +6687,7 @@ public class NotificationManagerService extends SystemService {
|
||||
// notification was a summary and its group key changed.
|
||||
if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
|
||||
cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
|
||||
null);
|
||||
null, REASON_APP_CANCEL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7892,7 +7892,7 @@ public class NotificationManagerService extends SystemService {
|
||||
final int M = canceledNotifications.size();
|
||||
for (int i = 0; i < M; i++) {
|
||||
cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
|
||||
listenerName, false /* sendDelete */, flagChecker);
|
||||
listenerName, false /* sendDelete */, flagChecker, reason);
|
||||
}
|
||||
updateLightsLocked();
|
||||
}
|
||||
@@ -7963,7 +7963,7 @@ public class NotificationManagerService extends SystemService {
|
||||
// Warning: The caller is responsible for invoking updateLightsLocked().
|
||||
@GuardedBy("mNotificationLock")
|
||||
private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
|
||||
String listenerName, boolean sendDelete, FlagChecker flagChecker) {
|
||||
String listenerName, boolean sendDelete, FlagChecker flagChecker, int reason) {
|
||||
Notification n = r.getNotification();
|
||||
if (!n.isGroupSummary()) {
|
||||
return;
|
||||
@@ -7977,30 +7977,33 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
|
||||
cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
|
||||
sendDelete, true, flagChecker);
|
||||
sendDelete, true, flagChecker, reason);
|
||||
cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
|
||||
listenerName, sendDelete, false, flagChecker);
|
||||
listenerName, sendDelete, false, flagChecker, reason);
|
||||
}
|
||||
|
||||
@GuardedBy("mNotificationLock")
|
||||
private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
|
||||
NotificationRecord parentNotification, int callingUid, int callingPid,
|
||||
String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) {
|
||||
String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker,
|
||||
int reason) {
|
||||
final String pkg = parentNotification.getSbn().getPackageName();
|
||||
final int userId = parentNotification.getUserId();
|
||||
final int reason = REASON_GROUP_SUMMARY_CANCELED;
|
||||
final int childReason = REASON_GROUP_SUMMARY_CANCELED;
|
||||
for (int i = notificationList.size() - 1; i >= 0; i--) {
|
||||
final NotificationRecord childR = notificationList.get(i);
|
||||
final StatusBarNotification childSbn = childR.getSbn();
|
||||
if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
|
||||
childR.getGroupKey().equals(parentNotification.getGroupKey())
|
||||
&& (childR.getFlags() & FLAG_FOREGROUND_SERVICE) == 0
|
||||
&& (flagChecker == null || flagChecker.apply(childR.getFlags()))) {
|
||||
&& (flagChecker == null || flagChecker.apply(childR.getFlags()))
|
||||
&& (!childR.getChannel().isImportantConversation()
|
||||
|| reason != REASON_CANCEL)) {
|
||||
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
|
||||
childSbn.getTag(), userId, 0, 0, reason, listenerName);
|
||||
childSbn.getTag(), userId, 0, 0, childReason, listenerName);
|
||||
notificationList.remove(i);
|
||||
mNotificationsByKey.remove(childR.getKey());
|
||||
cancelNotificationLocked(childR, sendDelete, reason, wasPosted, listenerName);
|
||||
cancelNotificationLocked(childR, sendDelete, childReason, wasPosted, listenerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user