Fix heads up/ambient content inflation w/ groups.

This CL fixes lazy content inflation for alerting notifications in
groups by inflating them on the fly if we would transfer to a
notification that does not have its content inflated. We introduce a
helper class here to explicitly deal with the transfer logic,
refactoring a lot of the GroupManager code out.

This removes the previous workaround to always inflate heads up +
ambient views.

Bug: 111809944
Fixes: 111809944
Fixes: 117933032
Fixes: 117894786
Test: runtest systemui
Test: manual, posted groups that trigger a transfer and a transfer back.
Group alerts behaved as expected.
Change-Id: I9b5ec4c8bdeea20707874d90213dcd1d22d8b503
This commit is contained in:
Kevin
2018-11-09 18:19:54 -08:00
parent 80c2e00636
commit 01a53cbfcd
18 changed files with 979 additions and 482 deletions

View File

@@ -52,6 +52,7 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -138,6 +139,8 @@ public class SystemUIFactory {
() -> new NotificationLockscreenUserManagerImpl(context));
providers.put(VisualStabilityManager.class, VisualStabilityManager::new);
providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
providers.put(NotificationGroupAlertTransferHelper.class,
NotificationGroupAlertTransferHelper::new);
providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
providers.put(AmbientPulseManager.class, () -> new AmbientPulseManager(context));

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.notification.NotificationData.Entry;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
@@ -27,7 +29,7 @@ import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import java.util.stream.Stream;
@@ -46,8 +48,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* NotificationManagerService side, but we keep it to prevent the UI from looking weird and
* will remove when possible. See {@link NotificationLifetimeExtender}
*/
protected final ArraySet<NotificationData.Entry> mExtendedLifetimeAlertEntries =
new ArraySet<>();
protected final ArraySet<Entry> mExtendedLifetimeAlertEntries = new ArraySet<>();
protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
protected int mMinimumDisplayTime;
@@ -60,7 +61,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* Adds the notification to be managed.
* @param entry entry to show
*/
public void showNotification(@NonNull NotificationData.Entry entry) {
public void showNotification(@NonNull Entry entry) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "showNotification");
}
@@ -139,7 +140,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @return the entry
*/
@Nullable
public NotificationData.Entry getEntry(@NonNull String key) {
public Entry getEntry(@NonNull String key) {
AlertEntry entry = mAlertEntries.get(key);
return entry != null ? entry.mEntry : null;
}
@@ -149,7 +150,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @return all entries
*/
@NonNull
public Stream<NotificationData.Entry> getAllEntries() {
public Stream<Entry> getAllEntries() {
return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -169,11 +170,18 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
return mAlertEntries.containsKey(key);
}
/**
* Gets the flag corresponding to the notification content view this alert manager will show.
*
* @return flag corresponding to the content view
*/
public abstract @InflationFlag int getContentFlag();
/**
* Add a new entry and begin managing it.
* @param entry the entry to add
*/
protected final void addAlertEntry(@NonNull NotificationData.Entry entry) {
protected final void addAlertEntry(@NonNull Entry entry) {
AlertEntry alertEntry = createAlertEntry();
alertEntry.setEntry(entry);
mAlertEntries.put(entry.key, alertEntry);
@@ -196,7 +204,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
if (alertEntry == null) {
return;
}
NotificationData.Entry entry = alertEntry.mEntry;
Entry entry = alertEntry.mEntry;
mAlertEntries.remove(key);
onAlertEntryRemoved(alertEntry);
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -243,12 +251,12 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
}
@Override
public boolean shouldExtendLifetime(NotificationData.Entry entry) {
public boolean shouldExtendLifetime(Entry entry) {
return !canRemoveImmediately(entry.key);
}
@Override
public void setShouldManageLifetime(NotificationData.Entry entry, boolean shouldExtend) {
public void setShouldManageLifetime(Entry entry, boolean shouldExtend) {
if (shouldExtend) {
mExtendedLifetimeAlertEntries.add(entry);
} else {
@@ -258,17 +266,17 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
///////////////////////////////////////////////////////////////////////////////////////////////
protected class AlertEntry implements Comparable<AlertEntry> {
@Nullable public NotificationData.Entry mEntry;
@Nullable public Entry mEntry;
public long mPostTime;
public long mEarliestRemovaltime;
@Nullable protected Runnable mRemoveAlertRunnable;
public void setEntry(@NonNull final NotificationData.Entry entry) {
public void setEntry(@NonNull final Entry entry) {
setEntry(entry, () -> removeAlertEntry(entry.key));
}
public void setEntry(@NonNull final NotificationData.Entry entry,
public void setEntry(@NonNull final Entry entry,
@Nullable Runnable removeAlertRunnable) {
mEntry = entry;
mRemoveAlertRunnable = removeAlertRunnable;

View File

@@ -26,6 +26,7 @@ import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
/**
* Manager which handles high priority notifications that should "pulse" in when the device is
@@ -71,6 +72,10 @@ public final class AmbientPulseManager extends AlertingNotificationManager {
topEntry.extendPulse();
}
public @InflationFlag int getContentFlag() {
return FLAG_CONTENT_VIEW_AMBIENT;
}
@Override
protected void onAlertEntryAdded(AlertEntry alertEntry) {
NotificationData.Entry entry = alertEntry.mEntry;

View File

@@ -84,6 +84,7 @@ import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -117,6 +118,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
private final NotificationGroupManager mGroupManager =
Dependency.get(NotificationGroupManager.class);
private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper =
Dependency.get(NotificationGroupAlertTransferHelper.class);
private final NotificationGutsManager mGutsManager =
Dependency.get(NotificationGutsManager.class);
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
@@ -247,7 +250,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
}
private void onPostInit() {
mGroupManager.setPendingEntries(mPendingNotifications);
mGroupAlertTransferHelper.setPendingEntries(mPendingNotifications);
mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper);
}
/**
@@ -506,6 +510,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
NotificationData.Entry addedEntry = mNotificationData.get(key);
if (addedEntry != null) {
addedEntry.abortTask();
mGroupAlertTransferHelper.onInflationAborted(addedEntry);
}
}
@@ -554,13 +559,18 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
mPendingNotifications.remove(entry.key);
// If there was an async task started after the removal, we don't want to add it back to
// the list, otherwise we might get leaks.
boolean isNew = mNotificationData.get(entry.key) == null;
if (isNew && !entry.row.isRemoved()) {
showAlertingView(entry, inflatedFlags);
addEntry(entry);
} else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
mVisualStabilityManager.onLowPriorityUpdated(entry);
mPresenter.updateNotificationViews();
if (!entry.row.isRemoved()) {
boolean isNew = mNotificationData.get(entry.key) == null;
if (isNew) {
showAlertingView(entry, inflatedFlags);
addEntry(entry);
} else {
if (entry.row.hasLowPriorityStateUpdated()) {
mVisualStabilityManager.onLowPriorityUpdated(entry);
mPresenter.updateNotificationViews();
}
mGroupAlertTransferHelper.onInflationFinished(entry);
}
}
entry.row.setLowPriorityStateUpdated(false);
}
@@ -823,7 +833,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
mNotificationData.getImportance(key));
mPendingNotifications.put(key, shadeEntry);
mGroupManager.onPendingEntryAdded(shadeEntry);
mGroupAlertTransferHelper.onPendingEntryAdded(shadeEntry);
}
@VisibleForTesting

View File

@@ -464,7 +464,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
* Inflate views based off the inflation flags set. Inflation happens asynchronously.
* Inflate views based off the inflation flags set. Inflation happens asynchronously.
*/
public void inflateViews() {
mNotificationInflater.inflateNotificationViews();
@@ -510,6 +510,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationInflater.updateInflationFlag(flag, shouldInflate);
}
/**
* Whether or not a content view should be inflated.
*
* @param flag the flag corresponding to the content view
* @return true if the flag is set, false otherwise
*/
public boolean isInflationFlagSet(@InflationFlag int flag) {
return mNotificationInflater.isInflationFlagSet(flag);
}
/**
* Caches whether or not this row contains a system notification. Note, this is only cached
* once per notification as the packageInfo can't technically change for a notification row.

View File

@@ -100,17 +100,13 @@ public class NotificationInflater {
public static final int FLAG_CONTENT_VIEW_ALL = ~0;
// TODO: Heads up and ambient are always inflated as a temporary workaround.
// See http://b/117933032 and http://b/117894786
/**
* Content views that must be inflated at all times.
*/
@InflationFlag
private static final int REQUIRED_INFLATION_FLAGS =
FLAG_CONTENT_VIEW_CONTRACTED
| FLAG_CONTENT_VIEW_EXPANDED
| FLAG_CONTENT_VIEW_HEADS_UP
| FLAG_CONTENT_VIEW_AMBIENT;
| FLAG_CONTENT_VIEW_EXPANDED;
/**
* The set of content views to inflate.
@@ -201,11 +197,12 @@ public class NotificationInflater {
}
/**
* Add flags for which content views should be inflated in addition to those already set.
* Convenience method for setting multiple flags at once.
*
* @param flags a set of {@link InflationFlag} corresponding to content views that should be
* inflated
*/
@VisibleForTesting
public void addInflationFlags(@InflationFlag int flags) {
mInflationFlags |= flags;
}
@@ -216,13 +213,12 @@ public class NotificationInflater {
* @param flag the {@link InflationFlag} corresponding to the view
* @return true if the flag is set and view will be inflated, false o/w
*/
@VisibleForTesting
public boolean isInflationFlagSet(@InflationFlag int flag) {
return ((mInflationFlags & flag) != 0);
}
/**
* Inflate all views of this notification on a background thread. This is asynchronous and will
* Inflate views for set flags on a background thread. This is asynchronous and will
* notify the callback once it's finished.
*/
public void inflateNotificationViews() {
@@ -234,7 +230,7 @@ public class NotificationInflater {
* will notify the callback once it's finished. If the content view is already inflated, this
* will reinflate it.
*
* @param reInflateFlags flags which views should be inflated. Should be a subset of
* @param reInflateFlags flags which views should be inflated. Should be a subset of
* {@link NotificationInflater#mInflationFlags} as only those will be
* inflated/reinflated.
*/

View File

@@ -4557,7 +4557,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setGroupManager(NotificationGroupManager groupManager) {
this.mGroupManager = groupManager;
mGroupManager.setOnGroupChangeListener(mOnGroupChangeListener);
mGroupManager.addOnGroupChangeListener(mOnGroupChangeListener);
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)

View File

@@ -0,0 +1,460 @@
/*
* Copyright (C) 2018 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.phone;
import android.annotation.NonNull;
import android.app.Notification;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListener;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
/**
* A helper class dealing with the alert interactions between {@link NotificationGroupManager},
* {@link HeadsUpManager}, {@link AmbientPulseManager}. In particular, this class deals with keeping
* the correct notification in a group alerting based off the group suppression.
*/
public class NotificationGroupAlertTransferHelper implements OnGroupChangeListener,
OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener {
private static final long ALERT_TRANSFER_TIMEOUT = 300;
/**
* The list of entries containing group alert metadata for each group. Keyed by group key.
*/
private final ArrayMap<String, GroupAlertEntry> mGroupAlertEntries = new ArrayMap<>();
/**
* The list of entries currently inflating that should alert after inflation. Keyed by
* notification key.
*/
private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>();
private HeadsUpManager mHeadsUpManager;
private final AmbientPulseManager mAmbientPulseManager =
Dependency.get(AmbientPulseManager.class);
private final NotificationGroupManager mGroupManager =
Dependency.get(NotificationGroupManager.class);
// TODO(b/119637830): It would be good if GroupManager already had all pending notifications as
// normal children (i.e. add notifications to GroupManager before inflation) so that we don't
// have to have this dependency. We'd also have to worry less about the suppression not being up
// to date.
/**
* Notifications that are currently inflating for the first time. Used to remove an incorrectly
* alerting notification faster.
*/
private HashMap<String, Entry> mPendingNotifications;
private boolean mIsDozing;
public NotificationGroupAlertTransferHelper() {
Dependency.get(StatusBarStateController.class).addListener(this);
}
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}
public void setPendingEntries(HashMap<String, Entry> pendingNotifications) {
mPendingNotifications = pendingNotifications;
}
@Override
public void onStateChanged(int newState) {}
@Override
public void onDozingChanged(boolean isDozing) {
if (mIsDozing != isDozing) {
for (GroupAlertEntry groupAlertEntry : mGroupAlertEntries.values()) {
groupAlertEntry.mLastAlertTransferTime = 0;
groupAlertEntry.mAlertSummaryOnNextAddition = false;
}
}
mIsDozing = isDozing;
}
@Override
public void onGroupCreated(NotificationGroup group, String groupKey) {
mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
}
@Override
public void onGroupRemoved(NotificationGroup group, String groupKey) {
mGroupAlertEntries.remove(groupKey);
}
@Override
public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
AlertingNotificationManager alertManager = getActiveAlertManager();
if (suppressed) {
if (alertManager.isAlerting(group.summary.key)) {
handleSuppressedSummaryAlerted(group.summary, alertManager);
}
} else {
// Group summary can be null if we are no longer suppressed because the summary was
// removed. In that case, we don't need to alert the summary.
if (group.summary == null) {
return;
}
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
group.summary.notification));
// Group is no longer suppressed. We should check if we need to transfer the alert
// back to the summary now that it's no longer suppressed.
if (groupAlertEntry.mAlertSummaryOnNextAddition) {
if (!alertManager.isAlerting(group.summary.key)) {
alertNotificationWhenPossible(group.summary, alertManager);
}
groupAlertEntry.mAlertSummaryOnNextAddition = false;
} else {
checkShouldTransferBack(groupAlertEntry);
}
}
}
@Override
public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
onAlertStateChanged(entry, isAmbient, mAmbientPulseManager);
}
@Override
public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
}
private void onAlertStateChanged(Entry entry, boolean isAlerting,
AlertingNotificationManager alertManager) {
if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.notification)) {
handleSuppressedSummaryAlerted(entry, alertManager);
}
}
/**
* Called when the entry's reinflation has finished. If there is an alert pending, we then
* show the alert.
*
* @param entry entry whose inflation has finished
*/
public void onInflationFinished(@NonNull Entry entry) {
PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key);
if (alertInfo != null) {
if (alertInfo.isStillValid()) {
alertNotificationWhenPossible(entry, getActiveAlertManager());
} else {
// The transfer is no longer valid. Free the content.
entry.row.freeContentViewWhenSafe(alertInfo.mAlertManager.getContentFlag());
}
}
}
/**
* Called when the entry's reinflation has been aborted.
*
* @param entry entry whose inflation has been aborted
*/
public void onInflationAborted(@NonNull Entry entry) {
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(
mGroupManager.getGroupKey(entry.notification));
if (groupAlertEntry == null) {
return;
}
mPendingAlerts.remove(entry.key);
}
/**
* Called when a new notification has been posted but is not inflated yet. We use this to see
* as early as we can if we need to abort a transfer.
*
* @param entry entry that has been added
*/
public void onPendingEntryAdded(@NonNull Entry entry) {
String groupKey = mGroupManager.getGroupKey(entry.notification);
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
if (groupAlertEntry != null) {
checkShouldTransferBack(groupAlertEntry);
}
}
/**
* Gets the number of new notifications pending inflation that will be added to the group
* but currently aren't and should not alert.
*
* @param group group to check
* @return the number of new notifications that will be added to the group
*/
private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) {
if (mPendingNotifications == null) {
return 0;
}
int number = 0;
Collection<Entry> values = mPendingNotifications.values();
for (Entry entry : values) {
if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
number++;
}
}
return number;
}
/**
* Checks if the pending inflations will add children to this group.
*
* @param group group to check
* @return true if a pending notification will add to this group
*/
private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) {
if (mPendingNotifications == null) {
return false;
}
Collection<Entry> values = mPendingNotifications.values();
for (Entry entry : values) {
if (isPendingNotificationInGroup(entry, group)) {
return true;
}
}
return false;
}
/**
* Checks if a new pending notification will be added to the group.
*
* @param entry pending notification
* @param group group to check
* @return true if the notification will add to the group, false o/w
*/
private boolean isPendingNotificationInGroup(@NonNull Entry entry,
@NonNull NotificationGroup group) {
String groupKey = mGroupManager.getGroupKey(group.summary.notification);
return mGroupManager.isGroupChild(entry.notification)
&& Objects.equals(mGroupManager.getGroupKey(entry.notification), groupKey)
&& !group.children.containsKey(entry.key);
}
/**
* Handles the scenario where a summary that has been suppressed is alerted. A suppressed
* summary should for all intents and purposes be invisible to the user and as a result should
* not alert. When this is the case, it is our responsibility to pass the alert to the
* appropriate child which will be the representative notification alerting for the group.
*
* @param summary the summary that is suppressed and alerting
* @param alertManager the alert manager that manages the alerting summary
*/
private void handleSuppressedSummaryAlerted(@NonNull Entry summary,
@NonNull AlertingNotificationManager alertManager) {
StatusBarNotification sbn = summary.notification;
GroupAlertEntry groupAlertEntry =
mGroupAlertEntries.get(mGroupManager.getGroupKey(sbn));
if (!mGroupManager.isSummaryOfSuppressedGroup(summary.notification)
|| !alertManager.isAlerting(sbn.getKey())
|| groupAlertEntry == null) {
return;
}
if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
// New children will actually be added to this group, let's not transfer the alert.
return;
}
Entry child = mGroupManager.getLogicalChildren(summary.notification).iterator().next();
if (child != null) {
if (child.row.keepInParent()
|| child.row.isRemoved()
|| child.row.isDismissed()) {
// The notification is actually already removed. No need to alert it.
return;
}
if (!alertManager.isAlerting(child.key) && onlySummaryAlerts(summary)) {
groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
}
transferAlertState(summary, child, alertManager);
}
}
/**
* Transfers the alert state one entry to another. We remove the alert from the first entry
* immediately to have the incorrect one up as short as possible. The second should alert
* when possible.
*
* @param fromEntry entry to transfer alert from
* @param toEntry entry to transfer to
* @param alertManager alert manager for the alert type
*/
private void transferAlertState(@NonNull Entry fromEntry, @NonNull Entry toEntry,
@NonNull AlertingNotificationManager alertManager) {
alertManager.removeNotification(fromEntry.key, true /* releaseImmediately */);
alertNotificationWhenPossible(toEntry, alertManager);
}
/**
* Determines if we need to transfer the alert back to the summary from the child and does
* so if needed.
*
* This can happen since notification groups are not delivered as a whole unit and it is
* possible we erroneously transfer the alert from the summary to the child even though
* more children are coming. Thus, if a child is added within a certain timeframe after we
* transfer, we back out and alert the summary again.
*
* @param groupAlertEntry group alert entry to check
*/
private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
< ALERT_TRANSFER_TIMEOUT) {
Entry summary = groupAlertEntry.mGroup.summary;
AlertingNotificationManager alertManager = getActiveAlertManager();
if (!onlySummaryAlerts(summary)) {
return;
}
ArrayList<Entry> children = mGroupManager.getLogicalChildren(summary.notification);
int numChildren = children.size();
int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
numChildren += numPendingChildren;
if (numChildren <= 1) {
return;
}
boolean releasedChild = false;
for (int i = 0; i < children.size(); i++) {
Entry entry = children.get(i);
if (onlySummaryAlerts(entry) && alertManager.isAlerting(entry.key)) {
releasedChild = true;
alertManager.removeNotification(entry.key, true /* releaseImmediately */);
}
if (mPendingAlerts.containsKey(entry.key)) {
// This is the child that would've been removed if it was inflated.
releasedChild = true;
mPendingAlerts.get(entry.key).mAbortOnInflation = true;
}
}
if (releasedChild && !alertManager.isAlerting(summary.key)) {
boolean notifyImmediately = (numChildren - numPendingChildren) > 1;
if (notifyImmediately) {
alertNotificationWhenPossible(summary, alertManager);
} else {
// Should wait until the pending child inflates before alerting.
groupAlertEntry.mAlertSummaryOnNextAddition = true;
}
groupAlertEntry.mLastAlertTransferTime = 0;
}
}
}
/**
* Tries to alert the notification. If its content view is not inflated, we inflate and continue
* when the entry finishes inflating the view.
*
* @param entry entry to show
* @param alertManager alert manager for the alert type
*/
private void alertNotificationWhenPossible(@NonNull Entry entry,
@NonNull AlertingNotificationManager alertManager) {
@InflationFlag int contentFlag = alertManager.getContentFlag();
if (!entry.row.isInflationFlagSet(contentFlag)) {
// Take in the current alert manager in case it changes.
mPendingAlerts.put(entry.key, new PendingAlertInfo(alertManager));
entry.row.updateInflationFlag(contentFlag, true /* shouldInflate */);
entry.row.inflateViews();
return;
}
if (alertManager.isAlerting(entry.key)) {
alertManager.updateNotification(entry.key, true /* alert */);
} else {
alertManager.showNotification(entry);
}
}
private AlertingNotificationManager getActiveAlertManager() {
return mIsDozing ? mAmbientPulseManager : mHeadsUpManager;
}
private boolean onlySummaryAlerts(Entry entry) {
return entry.notification.getNotification().getGroupAlertBehavior()
== Notification.GROUP_ALERT_SUMMARY;
}
/**
* Information about a pending alert used to determine if the alert is still needed when
* inflation completes.
*/
private class PendingAlertInfo {
final AlertingNotificationManager mAlertManager;
/**
* The notification is still pending inflation but we've decided that we no longer need
* the content view (e.g. suppression might have changed and we decided we need to transfer
* back). However, there is no way to abort just this inflation if other inflation requests
* have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
* we just flag it as aborted and free when it's inflated.
*/
boolean mAbortOnInflation;
PendingAlertInfo(AlertingNotificationManager alertManager) {
mAlertManager = alertManager;
}
/**
* Whether or not the pending alert is still valid and should still alert after inflation.
*
* @return true if the pending alert should still occur, false o/w
*/
private boolean isStillValid() {
if (mAbortOnInflation) {
// Notification is aborted due to the transfer being explicitly cancelled
return false;
}
if (mAlertManager != getActiveAlertManager()) {
// Alert manager has changed
return false;
}
return true;
}
}
/**
* Contains alert metadata for the notification group used to determine when/how the alert
* should be transferred.
*/
private static class GroupAlertEntry {
/**
* The time when the last alert transfer from summary to child happened.
*/
long mLastAlertTransferTime;
boolean mAlertSummaryOnNextAddition;
final NotificationGroup mGroup;
GroupAlertEntry(NotificationGroup group) {
this.mGroup = group;
}
}
}

View File

@@ -16,17 +16,12 @@
package com.android.systemui.statusbar.phone;
import android.app.Notification;
import android.os.SystemClock;
import android.annotation.Nullable;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListener;
import com.android.systemui.statusbar.StatusBarState;
@@ -40,9 +35,7 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
@@ -53,23 +46,25 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
OnAmbientChangedListener, StateListener {
private static final String TAG = "NotificationGroupManager";
private static final long ALERT_TRANSFER_TIMEOUT = 300;
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private OnGroupChangeListener mListener;
private final ArraySet<OnGroupChangeListener> mListeners = new ArraySet<>();
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
private AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
private boolean mIsDozing;
private boolean mIsUpdatingUnchangedGroup;
private HashMap<String, NotificationData.Entry> mPendingNotifications;
public NotificationGroupManager() {
Dependency.get(StatusBarStateController.class).addListener(this);
}
public void setOnGroupChangeListener(OnGroupChangeListener listener) {
mListener = listener;
/**
* Add a listener for changes to groups.
*
* @param listener listener to add
*/
public void addOnGroupChangeListener(OnGroupChangeListener listener) {
mListeners.add(listener);
}
public boolean isGroupExpanded(StatusBarNotification sbn) {
@@ -91,7 +86,10 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
private void setGroupExpanded(NotificationGroup group, boolean expanded) {
group.expanded = expanded;
if (group.summary != null) {
mListener.onGroupExpansionChanged(group.summary.row, expanded);
for (OnGroupChangeListener listener : mListeners) {
listener.onGroupExpansionChanged(group.summary.row,
expanded);
}
}
}
@@ -127,6 +125,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
if (group.children.isEmpty()) {
if (group.summary == null) {
mGroupMap.remove(groupKey);
for (OnGroupChangeListener listener : mListeners) {
listener.onGroupRemoved(group, groupKey);
}
}
}
}
@@ -142,6 +143,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
if (group == null) {
group = new NotificationGroup();
mGroupMap.put(groupKey, group);
for (OnGroupChangeListener listener : mListeners) {
listener.onGroupCreated(group, groupKey);
}
}
if (isGroupChild) {
NotificationData.Entry existing = group.children.get(added.key);
@@ -166,127 +170,13 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
for (NotificationData.Entry child : childrenCopy) {
onEntryBecomingChild(child);
}
mListener.onGroupCreatedFromChildren(group);
}
}
cleanUpAlertStatesOnAdd(group, false /* addIsPending */);
}
public void onPendingEntryAdded(NotificationData.Entry shadeEntry) {
String groupKey = getGroupKey(shadeEntry.notification);
NotificationGroup group = mGroupMap.get(groupKey);
if (group != null) {
cleanUpAlertStatesOnAdd(group, true /* addIsPending */);
}
}
/**
* Set whether or not the device is dozing. This allows the group manager to reset some
* specific alert state logic based off when the state changes.
* @param isDozing if the device is dozing.
*/
@VisibleForTesting
public void setDozing(boolean isDozing) {
if (mIsDozing != isDozing) {
for (NotificationGroup group : mGroupMap.values()) {
group.lastAlertTransfer = 0;
group.alertSummaryOnNextAddition = false;
}
}
mIsDozing = isDozing;
}
/**
* Clean up the alert states when a new child was added.
* @param group The group where a view was added or will be added.
* @param addIsPending True if is the addition still pending or false has it already been added.
*/
private void cleanUpAlertStatesOnAdd(NotificationGroup group, boolean addIsPending) {
AlertingNotificationManager alertManager =
mIsDozing ? mAmbientPulseManager : mHeadsUpManager;
if (!addIsPending && group.alertSummaryOnNextAddition) {
if (!alertManager.isAlerting(group.summary.key)) {
alertManager.showNotification(group.summary);
}
group.alertSummaryOnNextAddition = false;
}
// Because notification groups are not delivered as a whole unit, it may happen that a
// group child gets added quite a bit after the summary got posted. Our guidance is, that
// apps should always post the group summary as well and we'll hide it for them if the child
// is the only child in a group. Because of this, we also have to transfer alert to the
// child, otherwise the invisible summary would be alerted.
// This transfer to the child is not always correct in case the app has just posted another
// child in addition to the existing one, but it hasn't arrived in systemUI yet. In such
// a scenario we would transfer the alert to the old child and the wrong notification
// would be alerted. In order to avoid this, we'll recover from this issue and alert the
// summary again instead of the old child if it's within a certain timeout.
if (SystemClock.elapsedRealtime() - group.lastAlertTransfer < ALERT_TRANSFER_TIMEOUT) {
if (!onlySummaryAlerts(group.summary)) {
return;
}
int numChildren = group.children.size();
NotificationData.Entry isolatedChild = getIsolatedChild(getGroupKey(
group.summary.notification));
int numPendingChildren = getPendingChildrenNotAlerting(group);
numChildren += numPendingChildren;
if (isolatedChild != null) {
numChildren++;
}
if (numChildren <= 1) {
return;
}
boolean releasedChild = false;
ArrayList<NotificationData.Entry> children = new ArrayList<>(group.children.values());
int size = children.size();
for (int i = 0; i < size; i++) {
NotificationData.Entry entry = children.get(i);
if (onlySummaryAlerts(entry) && alertManager.isAlerting(entry.key)) {
releasedChild = true;
alertManager.removeNotification(entry.key, true /* releaseImmediately */);
for (OnGroupChangeListener listener : mListeners) {
listener.onGroupCreatedFromChildren(group);
}
}
if (isolatedChild != null && onlySummaryAlerts(isolatedChild)
&& alertManager.isAlerting(isolatedChild.key)) {
releasedChild = true;
alertManager.removeNotification(isolatedChild.key, true /* releaseImmediately */);
}
if (releasedChild && !alertManager.isAlerting(group.summary.key)) {
boolean notifyImmediately = (numChildren - numPendingChildren) > 1;
if (notifyImmediately) {
alertManager.showNotification(group.summary);
} else {
group.alertSummaryOnNextAddition = true;
}
group.lastAlertTransfer = 0;
}
}
}
private int getPendingChildrenNotAlerting(NotificationGroup group) {
if (mPendingNotifications == null) {
return 0;
}
int number = 0;
String groupKey = getGroupKey(group.summary.notification);
Collection<NotificationData.Entry> values = mPendingNotifications.values();
for (NotificationData.Entry entry : values) {
if (!isGroupChild(entry.notification)) {
continue;
}
if (!Objects.equals(getGroupKey(entry.notification), groupKey)) {
continue;
}
if (group.children.containsKey(entry.key)) {
continue;
}
if (onlySummaryAlerts(entry)) {
number++;
}
}
return number;
}
private void onEntryBecomingChild(NotificationData.Entry entry) {
if (shouldIsolate(entry)) {
isolateNotification(entry);
@@ -304,16 +194,12 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
&& group.summary.notification.getNotification().isGroupSummary()
&& hasIsolatedChildren(group)));
if (prevSuppressed != group.suppressed) {
if (group.suppressed) {
if (mHeadsUpManager.isAlerting(group.summary.key)) {
handleSuppressedSummaryAlerted(group.summary, mHeadsUpManager);
} else if (mAmbientPulseManager.isAlerting(group.summary.key)) {
handleSuppressedSummaryAlerted(group.summary, mAmbientPulseManager);
for (OnGroupChangeListener listener : mListeners) {
if (!mIsUpdatingUnchangedGroup) {
listener.onGroupSuppressionChanged(group, group.suppressed);
listener.onGroupsChanged();
}
}
if (!mIsUpdatingUnchangedGroup && mListener != null) {
mListener.onGroupsChanged();
}
}
}
@@ -462,8 +348,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
* but the logical summary, i.e when a child is isolated, it still returns the summary as if
* it wasn't isolated.
*/
public ExpandableNotificationRow getLogicalGroupSummary(
StatusBarNotification sbn) {
public ExpandableNotificationRow getLogicalGroupSummary(StatusBarNotification sbn) {
return getGroupSummary(sbn.getGroupKey());
}
@@ -475,6 +360,39 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
: group.summary.row;
}
/**
* Get the children that are logically in the summary's group, whether or not they are isolated.
*
* @param summary summary of a group
* @return list of the children
*/
public ArrayList<NotificationData.Entry> getLogicalChildren(StatusBarNotification summary) {
NotificationGroup group = mGroupMap.get(summary.getGroupKey());
if (group == null) {
return null;
}
ArrayList<NotificationData.Entry> children = new ArrayList<>(group.children.values());
NotificationData.Entry isolatedChild = getIsolatedChild(summary.getGroupKey());
if (isolatedChild != null) {
children.add(isolatedChild);
}
return children;
}
/**
* 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
*/
public String getGroupKey(StatusBarNotification sbn) {
if (isIsolated(sbn)) {
return sbn.getKey();
}
return sbn.getGroupKey();
}
/** @return group expansion state after toggling. */
public boolean toggleGroupExpansion(StatusBarNotification sbn) {
NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
@@ -489,27 +407,32 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
return mIsolatedEntries.containsKey(sbn.getKey());
}
private boolean isGroupSummary(StatusBarNotification sbn) {
/**
* Whether a notification is visually a group summary.
*
* @param sbn notification to check
* @return true if it is visually a group summary
*/
public boolean isGroupSummary(StatusBarNotification sbn) {
if (isIsolated(sbn)) {
return true;
}
return sbn.getNotification().isGroupSummary();
}
private boolean isGroupChild(StatusBarNotification sbn) {
/**
* Whether a notification is visually a group child.
*
* @param sbn notification to check
* @return true if it is visually a group child
*/
public boolean isGroupChild(StatusBarNotification sbn) {
if (isIsolated(sbn)) {
return false;
}
return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
}
private String getGroupKey(StatusBarNotification sbn) {
if (isIsolated(sbn)) {
return sbn.getKey();
}
return sbn.getGroupKey();
}
@Override
public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
}
@@ -524,123 +447,24 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
@Override
public void onAmbientStateChanged(NotificationData.Entry entry, boolean isAmbient) {
onAlertStateChanged(entry, isAmbient, mAmbientPulseManager);
onAlertStateChanged(entry, isAmbient);
}
@Override
public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
onAlertStateChanged(entry, isHeadsUp);
}
private void onAlertStateChanged(NotificationData.Entry entry, boolean isAlerting,
AlertingNotificationManager alertManager) {
final StatusBarNotification sbn = entry.notification;
private void onAlertStateChanged(NotificationData.Entry entry, boolean isAlerting) {
if (isAlerting) {
if (shouldIsolate(entry)) {
isolateNotification(entry);
} else if (sbn.getNotification().isGroupSummary()
&& isGroupSuppressed(sbn.getGroupKey())){
handleSuppressedSummaryAlerted(entry, alertManager);
}
} else {
stopIsolatingNotification(entry);
}
}
/**
* Handles the scenario where a summary that has been suppressed is alerted. A suppressed
* summary should for all intents and purposes be invisible to the user and as a result should
* not alert. When this is the case, it is our responsibility to pass the alert to the
* appropriate child which will be the representative notification alerting for the group.
* @param summary the summary that is suppressed and alerting
* @param alertManager the alert manager that manages the alerting summary
*/
private void handleSuppressedSummaryAlerted(@NonNull NotificationData.Entry summary,
@NonNull AlertingNotificationManager alertManager) {
StatusBarNotification sbn = summary.notification;
if (!isGroupSuppressed(sbn.getGroupKey())
|| !sbn.getNotification().isGroupSummary()
|| !alertManager.isAlerting(sbn.getKey())) {
return;
}
// The parent of a suppressed group got alerted, lets alert the child!
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
if (notificationGroup != null) {
if (pendingInflationsWillAddChildren(notificationGroup)) {
// New children will actually be added to this group, let's not transfer the alert.
return;
}
Iterator<NotificationData.Entry> iterator
= notificationGroup.children.values().iterator();
NotificationData.Entry child = iterator.hasNext() ? iterator.next() : null;
if (child == null) {
child = getIsolatedChild(sbn.getGroupKey());
}
if (child != null) {
if (child.row.keepInParent() || child.row.isRemoved() || child.row.isDismissed()) {
// the notification is actually already removed, no need to do alert on it.
return;
}
transferAlertStateToChild(summary, child, alertManager);
}
}
}
/**
* Transfers the alert state from a given summary notification to the specified child. The
* result is the child will now alert while the summary does not.
*
* @param summary the currently alerting summary notification
* @param child the child that should receive the alert
* @param alertManager the manager for the alert
*/
private void transferAlertStateToChild(@NonNull NotificationData.Entry summary,
@NonNull NotificationData.Entry child,
@NonNull AlertingNotificationManager alertManager) {
NotificationGroup notificationGroup = mGroupMap.get(summary.notification.getGroupKey());
if (alertManager.isAlerting(child.key)) {
alertManager.updateNotification(child.key, true /* alert */);
} else {
if (onlySummaryAlerts(summary)) {
notificationGroup.lastAlertTransfer = SystemClock.elapsedRealtime();
}
alertManager.showNotification(child);
}
alertManager.removeNotification(summary.key, true /* releaseImmediately */);
}
private boolean onlySummaryAlerts(NotificationData.Entry entry) {
return entry.notification.getNotification().getGroupAlertBehavior()
== Notification.GROUP_ALERT_SUMMARY;
}
/**
* Check if the pending inflations will add children to this group.
* @param group The group to check.
*/
private boolean pendingInflationsWillAddChildren(NotificationGroup group) {
if (mPendingNotifications == null) {
return false;
}
Collection<NotificationData.Entry> values = mPendingNotifications.values();
String groupKey = getGroupKey(group.summary.notification);
for (NotificationData.Entry entry : values) {
if (!isGroupChild(entry.notification)) {
continue;
}
if (!Objects.equals(getGroupKey(entry.notification), groupKey)) {
continue;
}
if (!group.children.containsKey(entry.key)) {
return true;
}
}
return false;
}
/**
* 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
@@ -656,10 +480,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
return false;
}
if (!mIsDozing && !mHeadsUpManager.isAlerting(entry.key)) {
return false;
}
if (mIsDozing && !mAmbientPulseManager.isAlerting(entry.key)) {
if (!mHeadsUpManager.isAlerting(entry.key) && !mAmbientPulseManager.isAlerting(entry.key)) {
return false;
}
return (sbn.getNotification().fullScreenIntent != null
@@ -687,7 +508,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
// When the notification gets added afterwards it is already isolated and therefore
// it doesn't lead to an update.
updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
mListener.onGroupsChanged();
for (OnGroupChangeListener listener : mListeners) {
listener.onGroupsChanged();
}
}
/**
@@ -702,7 +525,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
onEntryRemovedInternal(entry, entry.notification);
mIsolatedEntries.remove(sbn.getKey());
onEntryAdded(entry);
mListener.onGroupsChanged();
for (OnGroupChangeListener listener : mListeners) {
listener.onGroupsChanged();
}
}
}
@@ -729,20 +554,11 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
}
public void setPendingEntries(HashMap<String, NotificationData.Entry> pendingNotifications) {
mPendingNotifications = pendingNotifications;
}
@Override
public void onStateChanged(int newState) {
setStatusBarState(newState);
}
@Override
public void onDozingChanged(boolean isDozing) {
setDozing(isDozing);
}
public static class NotificationGroup {
public final HashMap<String, NotificationData.Entry> children = new HashMap<>();
public NotificationData.Entry summary;
@@ -751,12 +567,6 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
* Is this notification group suppressed, i.e its summary is hidden
*/
public boolean suppressed;
/**
* The time when the last alert transfer from group to child happened, while the summary
* has the flags to alert up on its own.
*/
public long lastAlertTransfer;
public boolean alertSummaryOnNextAddition;
@Override
public String toString() {
@@ -777,13 +587,39 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
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 expansion of a group has changed.
*
* @param changedRow the row for which the expansion has changed, which is also the summary
* @param expanded a boolean indicating the new expanded state
*/
void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
default void onGroupExpansionChanged(ExpandableNotificationRow changedRow,
boolean expanded) {}
/**
* A group of children just received a summary notification and should therefore become
@@ -791,12 +627,12 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
*
* @param group the group created
*/
void onGroupCreatedFromChildren(NotificationGroup group);
default void onGroupCreatedFromChildren(NotificationGroup group) {}
/**
* The groups have changed. This can happen if the isolation of a child has changes or if a
* group became suppressed / unsuppressed
*/
void onGroupsChanged();
default void onGroupsChanged() {}
}
}

View File

@@ -596,6 +596,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void start() {
mGroupManager = Dependency.get(NotificationGroupManager.class);
mGroupAlertTransferHelper = Dependency.get(NotificationGroupAlertTransferHelper.class);
mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
mNotificationLogger = Dependency.get(NotificationLogger.class);
mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
@@ -832,11 +833,14 @@ public class StatusBar extends SystemUI implements DemoMode,
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mGroupAlertTransferHelper);
mHeadsUpManager.addListener(mVisualStabilityManager);
mAmbientPulseManager.addListener(this);
mAmbientPulseManager.addListener(mGroupManager);
mAmbientPulseManager.addListener(mGroupAlertTransferHelper);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
putComponent(HeadsUpManager.class, mHeadsUpManager);
@@ -4105,6 +4109,8 @@ public class StatusBar extends SystemUI implements DemoMode,
protected NotificationGroupManager mGroupManager;
protected NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
// for heads up notifications
protected HeadsUpManagerPhone mHeadsUpManager;

View File

@@ -33,6 +33,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -136,6 +137,10 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
}
}
public @InflationFlag int getContentFlag() {
return FLAG_CONTENT_VIEW_HEADS_UP;
}
@Override
protected void onAlertEntryAdded(AlertEntry alertEntry) {
NotificationData.Entry entry = alertEntry.mEntry;

View File

@@ -17,6 +17,8 @@
package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_CONTRACTED;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -84,6 +86,11 @@ public class AlertingNotificationManagerTest extends SysuiTestCase {
@Override
protected void onAlertEntryRemoved(AlertEntry alertEntry) {}
@Override
public int getContentFlag() {
return FLAG_CONTENT_VIEW_CONTRACTED;
}
}
protected AlertingNotificationManager createAlertingNotificationManager() {

View File

@@ -53,7 +53,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationChildrenCon
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -140,10 +139,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
verify(row).updateShelfIconColor();
}
// TODO: Ignoring as a temporary workaround until heads up views can be safely freed.
// See http://b/117933032
@Test
@Ignore
public void testFreeContentViewWhenSafe() throws Exception {
ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);

View File

@@ -117,10 +117,7 @@ public class NotificationInflaterTest extends SysuiTestCase {
verify(mRow).onNotificationUpdated();
}
// TODO: Ignoring as a temporary workaround until ambient views can be safely freed.
// See http://b/117894786
@Test
@Ignore
public void testInflationOnlyInflatesSetFlags() throws Exception {
mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP,
true /* shouldInflate */);

View File

@@ -0,0 +1,204 @@
/*
* Copyright (C) 2018 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.phone;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.HashMap;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
private NotificationGroupManager mGroupManager;
private AmbientPulseManager mAmbientPulseManager;
private HeadsUpManager mHeadsUpManager;
private final HashMap<String, Entry> mPendingEntries = new HashMap<>();
private final NotificationGroupTestHelper mGroupTestHelper =
new NotificationGroupTestHelper(mContext);
@Before
public void setup() {
mAmbientPulseManager = new AmbientPulseManager(mContext);
mDependency.injectTestDependency(AmbientPulseManager.class, mAmbientPulseManager);
mHeadsUpManager = new HeadsUpManager(mContext) {};
mGroupManager = new NotificationGroupManager();
mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper();
mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
mGroupAlertTransferHelper.setPendingEntries(mPendingEntries);
mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper);
mHeadsUpManager.addListener(mGroupAlertTransferHelper);
mAmbientPulseManager.addListener(mGroupAlertTransferHelper);
}
@Test
public void testSuppressedSummaryHeadsUpTransfersToChild() {
Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
Entry childEntry = mGroupTestHelper.createChildNotification();
// Summary will be suppressed because there is only one child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// A suppressed summary should transfer its alert state to the child.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key));
assertTrue(mHeadsUpManager.isAlerting(childEntry.key));
}
@Test
public void testSuppressedSummaryHeadsUpTransfersToChildButBackAgain() {
NotificationData.Entry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.key, childEntry2);
mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// The alert state should transfer back to the summary as there is now more than one
// child and the summary should no longer be suppressed.
assertTrue(mHeadsUpManager.isAlerting(summaryEntry.key));
assertFalse(mHeadsUpManager.isAlerting(childEntry.key));
}
@Test
public void testSuppressedSummaryHeadsUpDoesntTransferBackOnDozingChanged() {
NotificationData.Entry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Set dozing to true.
mGroupAlertTransferHelper.onDozingChanged(true);
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.key, childEntry2);
mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// Dozing changed so no reason to re-alert summary.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key));
}
@Test
public void testSuppressedSummaryHeadsUpTransferDoesNotAlertChildIfUninflated() {
Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
Entry childEntry = mGroupTestHelper.createChildNotification();
when(childEntry.row.isInflationFlagSet(mHeadsUpManager.getContentFlag())).thenReturn(false);
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Alert is immediately removed from summary, but we do not show child yet either as its
// content is not inflated.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key));
assertFalse(mHeadsUpManager.isAlerting(childEntry.key));
}
@Test
public void testSuppressedSummaryHeadsUpTransferAlertsChildOnInflation() {
Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
Entry childEntry = mGroupTestHelper.createChildNotification();
when(childEntry.row.isInflationFlagSet(mHeadsUpManager.getContentFlag())).thenReturn(false);
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
when(childEntry.row.isInflationFlagSet(mHeadsUpManager.getContentFlag())).thenReturn(true);
mGroupAlertTransferHelper.onInflationFinished(childEntry);
// Alert is immediately removed from summary, and we show child as its content is inflated.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key));
assertTrue(mHeadsUpManager.isAlerting(childEntry.key));
}
@Test
public void testSuppressedSummaryHeadsUpTransferBackAbortsChildInflation() {
NotificationData.Entry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
when(childEntry.row.isInflationFlagSet(mHeadsUpManager.getContentFlag())).thenReturn(false);
NotificationData.Entry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.key, childEntry2);
mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// Child entry finishes its inflation.
when(childEntry.row.isInflationFlagSet(mHeadsUpManager.getContentFlag())).thenReturn(true);
mGroupAlertTransferHelper.onInflationFinished(childEntry);
verify(childEntry.row, times(1)).freeContentViewWhenSafe(mHeadsUpManager.getContentFlag());
assertFalse(mHeadsUpManager.isAlerting(childEntry.key));
}
}

View File

@@ -21,25 +21,15 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.Notification;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
@@ -58,11 +48,9 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
private static final String TEST_CHANNEL_ID = "test_channel";
private static final String TEST_GROUP_ID = "test_group";
private static final String TEST_PACKAGE_NAME = "test_pkg";
private NotificationGroupManager mGroupManager;
private int mId = 0;
private final NotificationGroupTestHelper mGroupTestHelper =
new NotificationGroupTestHelper(mContext);
@Mock HeadsUpManager mHeadsUpManager;
@Mock AmbientPulseManager mAmbientPulseManager;
@@ -77,13 +65,12 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
private void initializeGroupManager() {
mGroupManager = new NotificationGroupManager();
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setOnGroupChangeListener(mock(OnGroupChangeListener.class));
}
@Test
public void testIsOnlyChildInGroup() {
NotificationData.Entry childEntry = createChildNotification();
NotificationData.Entry summaryEntry = createSummaryNotification();
NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
@@ -93,24 +80,24 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
@Test
public void testIsChildInGroupWithSummary() {
NotificationData.Entry childEntry = createChildNotification();
NotificationData.Entry summaryEntry = createSummaryNotification();
NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(createChildNotification());
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
assertTrue(mGroupManager.isChildInGroupWithSummary(childEntry.notification));
}
@Test
public void testIsSummaryOfGroupWithChildren() {
NotificationData.Entry childEntry = createChildNotification();
NotificationData.Entry summaryEntry = createSummaryNotification();
NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(createChildNotification());
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
assertTrue(mGroupManager.isSummaryOfGroup(summaryEntry.notification));
assertEquals(summaryEntry.row, mGroupManager.getGroupSummary(childEntry.notification));
@@ -118,11 +105,11 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
@Test
public void testRemoveChildFromGroupWithSummary() {
NotificationData.Entry childEntry = createChildNotification();
NotificationData.Entry summaryEntry = createSummaryNotification();
NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(createChildNotification());
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
mGroupManager.onEntryRemoved(childEntry);
@@ -131,11 +118,11 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
@Test
public void testRemoveSummaryFromGroupWithSummary() {
NotificationData.Entry childEntry = createChildNotification();
NotificationData.Entry summaryEntry = createSummaryNotification();
NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(createChildNotification());
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
mGroupManager.onEntryRemoved(summaryEntry);
@@ -145,11 +132,11 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
@Test
public void testHeadsUpEntryIsIsolated() {
NotificationData.Entry childEntry = createChildNotification();
NotificationData.Entry summaryEntry = createSummaryNotification();
NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(createChildNotification());
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
when(mHeadsUpManager.isAlerting(childEntry.key)).thenReturn(true);
mGroupManager.onHeadsUpStateChanged(childEntry, true);
@@ -163,12 +150,11 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
@Test
public void testAmbientPulseEntryIsIsolated() {
mGroupManager.setDozing(true);
NotificationData.Entry childEntry = createChildNotification();
NotificationData.Entry summaryEntry = createSummaryNotification();
NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(createChildNotification());
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
when(mAmbientPulseManager.isAlerting(childEntry.key)).thenReturn(true);
mGroupManager.onAmbientStateChanged(childEntry, true);
@@ -179,128 +165,4 @@ public class NotificationGroupManagerTest extends SysuiTestCase {
assertEquals(summaryEntry.row,
mGroupManager.getLogicalGroupSummary(childEntry.notification));
}
@Test
public void testSuppressedSummaryHeadsUpTransfersToChild() {
NotificationData.Entry summaryEntry = createSummaryNotification();
when(mHeadsUpManager.isAlerting(summaryEntry.key)).thenReturn(true);
NotificationData.Entry childEntry = createChildNotification();
// Summary will be suppressed because there is only one child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// A suppressed summary should transfer its heads up state to the child.
verify(mHeadsUpManager, never()).showNotification(summaryEntry);
verify(mHeadsUpManager).showNotification(childEntry);
}
@Test
public void testSuppressedSummaryHeadsUpTransfersToChildButBackAgain() {
mHeadsUpManager = new HeadsUpManager(mContext) {};
mGroupManager.setHeadsUpManager(mHeadsUpManager);
NotificationData.Entry summaryEntry =
createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry =
createChildNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry2 =
createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of heads up state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Add second child notification so that summary is no longer suppressed.
mGroupManager.onEntryAdded(childEntry2);
// The heads up state should transfer back to the summary as there is now more than one
// child and the summary should no longer be suppressed.
assertTrue(mHeadsUpManager.isAlerting(summaryEntry.key));
assertFalse(mHeadsUpManager.isAlerting(childEntry.key));
}
@Test
public void testSuppressedSummaryAmbientPulseTransfersToChild() {
mGroupManager.setDozing(true);
NotificationData.Entry summaryEntry = createSummaryNotification();
when(mAmbientPulseManager.isAlerting(summaryEntry.key)).thenReturn(true);
NotificationData.Entry childEntry = createChildNotification();
// Summary will be suppressed because there is only one child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// A suppressed summary should transfer its ambient state to the child.
verify(mAmbientPulseManager, never()).showNotification(summaryEntry);
verify(mAmbientPulseManager).showNotification(childEntry);
}
@Test
public void testSuppressedSummaryAmbientPulseTransfersToChildButBackAgain() {
mGroupManager.setDozing(true);
mAmbientPulseManager = new AmbientPulseManager(mContext);
mDependency.injectTestDependency(AmbientPulseManager.class, mAmbientPulseManager);
initializeGroupManager();
NotificationData.Entry summaryEntry =
createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry =
createChildNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationData.Entry childEntry2 =
createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mAmbientPulseManager.showNotification(summaryEntry);
// Trigger a transfer of ambient state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Add second child notification so that summary is no longer suppressed.
mGroupManager.onEntryAdded(childEntry2);
// The ambient state should transfer back to the summary as there is now more than one
// child and the summary should no longer be suppressed.
assertTrue(mAmbientPulseManager.isAlerting(summaryEntry.key));
assertFalse(mAmbientPulseManager.isAlerting(childEntry.key));
}
private NotificationData.Entry createSummaryNotification() {
return createSummaryNotification(Notification.GROUP_ALERT_ALL);
}
private NotificationData.Entry createSummaryNotification(int groupAlertBehavior) {
return createEntry(true, groupAlertBehavior);
}
private NotificationData.Entry createChildNotification() {
return createChildNotification(Notification.GROUP_ALERT_ALL);
}
private NotificationData.Entry createChildNotification(int groupAlertBehavior) {
return createEntry(false, groupAlertBehavior);
}
private NotificationData.Entry createEntry(boolean isSummary, int groupAlertBehavior) {
Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("Title")
.setSmallIcon(R.drawable.ic_person)
.setGroupAlertBehavior(groupAlertBehavior)
.setGroupSummary(isSummary)
.setGroup(TEST_GROUP_ID)
.build();
StatusBarNotification sbn = new StatusBarNotification(
TEST_PACKAGE_NAME /* pkg */,
TEST_PACKAGE_NAME,
mId++,
null /* tag */,
0, /* uid */
0 /* initialPid */,
notif,
new UserHandle(ActivityManager.getCurrentUser()),
null /* overrideGroupKey */,
0 /* postTime */);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
entry.row = row;
when(row.getEntry()).thenReturn(entry);
when(row.getStatusBarNotification()).thenReturn(sbn);
return entry;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2018 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.phone;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
* Helper class for creating groups/summaries without having to inflate them.
*/
public final class NotificationGroupTestHelper {
private static final String TEST_CHANNEL_ID = "test_channel";
private static final String TEST_GROUP_ID = "test_group";
private static final String TEST_PACKAGE_NAME = "test_pkg";
private int mId = 0;
private final Context mContext;
public NotificationGroupTestHelper(Context context) {
mContext = context;
}
public NotificationData.Entry createSummaryNotification() {
return createSummaryNotification(Notification.GROUP_ALERT_ALL);
}
public NotificationData.Entry createSummaryNotification(int groupAlertBehavior) {
return createEntry(true, groupAlertBehavior);
}
public NotificationData.Entry createChildNotification() {
return createChildNotification(Notification.GROUP_ALERT_ALL);
}
public NotificationData.Entry createChildNotification(int groupAlertBehavior) {
return createEntry(false, groupAlertBehavior);
}
public NotificationData.Entry createEntry(boolean isSummary, int groupAlertBehavior) {
Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("Title")
.setSmallIcon(R.drawable.ic_person)
.setGroupAlertBehavior(groupAlertBehavior)
.setGroupSummary(isSummary)
.setGroup(TEST_GROUP_ID)
.build();
StatusBarNotification sbn = new StatusBarNotification(
TEST_PACKAGE_NAME /* pkg */,
TEST_PACKAGE_NAME,
mId++,
null /* tag */,
0, /* uid */
0 /* initialPid */,
notif,
new UserHandle(ActivityManager.getCurrentUser()),
null /* overrideGroupKey */,
0 /* postTime */);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
entry.row = row;
when(row.getEntry()).thenReturn(entry);
when(row.getStatusBarNotification()).thenReturn(sbn);
when(row.isInflationFlagSet(anyInt())).thenReturn(true);
return entry;
}
}

View File

@@ -209,11 +209,11 @@ public class StatusBarTest extends SysuiTestCase {
mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
mEntryManager, mScrimController, mBiometricUnlockController,
mKeyguardViewMediator, mRemoteInputManager, mock(NotificationGroupManager.class),
mock(FalsingManager.class), mock(StatusBarWindowController.class),
mock(NotificationIconAreaController.class), mock(DozeScrimController.class),
mock(NotificationShelf.class), mLockscreenUserManager,
mCommandQueue,
mNotificationPresenter, mock(BubbleController.class));
mock(NotificationGroupAlertTransferHelper.class), mock(FalsingManager.class),
mock(StatusBarWindowController.class), mock(NotificationIconAreaController.class),
mock(DozeScrimController.class), mock(NotificationShelf.class),
mLockscreenUserManager, mCommandQueue, mNotificationPresenter,
mock(BubbleController.class));
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mContext.getComponents();
mStatusBar.putComponent(StatusBar.class, mStatusBar);
@@ -634,6 +634,7 @@ public class StatusBarTest extends SysuiTestCase {
KeyguardViewMediator keyguardViewMediator,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationGroupManager notificationGroupManager,
NotificationGroupAlertTransferHelper notificationGroupAlertTransferHelper,
FalsingManager falsingManager,
StatusBarWindowController statusBarWindowController,
NotificationIconAreaController notificationIconAreaController,
@@ -662,6 +663,7 @@ public class StatusBarTest extends SysuiTestCase {
mKeyguardViewMediator = keyguardViewMediator;
mRemoteInputManager = notificationRemoteInputManager;
mGroupManager = notificationGroupManager;
mGroupAlertTransferHelper = notificationGroupAlertTransferHelper;
mFalsingManager = falsingManager;
mStatusBarWindowController = statusBarWindowController;
mNotificationIconAreaController = notificationIconAreaController;