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:
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 */);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user