Merge "Split ambient pulse notif logic from heads up."
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 47a6c52..24dcd3e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -146,19 +146,29 @@
<!-- Should "LTE"/"4G" be shown instead of "LTE+"/"4G+" when on NETWORK_TYPE_LTE_CA? -->
<bool name="config_hideLtePlus">false</bool>
- <!-- milliseconds before the heads up notification auto-dismisses. -->
+ <!-- The number of milliseconds before the heads up notification auto-dismisses. -->
<integer name="heads_up_notification_decay">5000</integer>
- <!-- milliseconds after a heads up notification is pushed back
+ <!-- The number of milliseconds after a heads up notification is pushed back
before the app can interrupt again. -->
<integer name="heads_up_default_snooze_length_ms">60000</integer>
<!-- Minimum display time for a heads up notification, in milliseconds. -->
<integer name="heads_up_notification_minimum_time">2000</integer>
- <!-- milliseconds before the heads up notification accepts touches. -->
+ <!-- The number of milliseconds before the heads up notification accepts touches. -->
<integer name="touch_acceptance_delay">700</integer>
+ <!-- The number of milliseconds before the ambient notification auto-dismisses. This will
+ override the default pulse length. -->
+ <integer name="ambient_notification_decay">6000</integer>
+
+ <!-- Minimum display time for a heads up notification, in milliseconds. -->
+ <integer name="ambient_notification_minimum_time">2000</integer>
+
+ <!-- The number of milliseconds to extend ambient pulse by when prompted (e.g. on touch) -->
+ <integer name="ambient_notification_extension_time">6000</integer>
+
<!-- The duration in seconds to wait before the dismiss buttons are shown. -->
<integer name="recents_task_bar_dismiss_delay_seconds">1000</integer>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 04d72ce..258b6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -30,6 +30,7 @@
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
@@ -137,6 +138,7 @@
providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
+ providers.put(AmbientPulseManager.class, () -> new AmbientPulseManager(context));
providers.put(NotificationBlockingHelperManager.class,
() -> new NotificationBlockingHelperManager(context));
providers.put(NotificationRemoteInputManager.class,
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index d6a1cf0..5739c99 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -16,7 +16,6 @@
package com.android.systemui.car;
import android.content.Context;
-import android.service.notification.StatusBarNotification;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.NotificationData;
@@ -41,7 +40,7 @@
}
@Override
- public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ public boolean shouldHeadsUp(NotificationData.Entry entry) {
// Because space is usually constrained in the auto use-case, there should not be a
// pinned notification when the shade has been expanded. Ensure this by not pinning any
// notification if the shade is already opened.
@@ -49,6 +48,6 @@
return false;
}
- return super.shouldPeek(entry, sbn);
+ return super.shouldHeadsUp(entry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 6a29299..bb05980 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -48,7 +48,15 @@
void onIgnoreTouchWhilePulsing(boolean ignore);
interface Callback {
- default void onNotificationHeadsUp() {}
+ /**
+ * Called when a high priority notification is added.
+ */
+ default void onNotificationAlerted() {}
+
+ /**
+ * Called when battery state or power save mode changes.
+ * @param active whether power save is active or not
+ */
default void onPowerSaveChanged(boolean active) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 1589969..31548b9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -409,7 +409,7 @@
private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
@Override
- public void onNotificationHeadsUp() {
+ public void onNotificationAlerted() {
onNotification();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 4516518..b6e88d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -165,7 +165,7 @@
* Whether or not the given notification is alerting and managed by this manager.
* @return true if the notification is alerting
*/
- public boolean contains(@NonNull String key) {
+ public boolean isAlerting(@NonNull String key) {
return mAlertEntries.containsKey(key);
}
@@ -294,7 +294,7 @@
removeAutoRemovalCallbacks();
if (!isSticky()) {
- long finishTime = mPostTime + mAutoDismissNotificationDecay;
+ long finishTime = calculateFinishTime();
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
mHandler.postDelayed(mRemoveAlertRunnable, removeDelay);
}
@@ -357,6 +357,14 @@
protected long calculatePostTime() {
return mClock.currentTimeMillis();
}
+
+ /**
+ * Calculate when the notification should auto-dismiss itself.
+ * @return the finish time
+ */
+ protected long calculateFinishTime() {
+ return mPostTime + mAutoDismissNotificationDecay;
+ }
}
protected final static class Clock {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
new file mode 100644
index 0000000..2c384d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
@@ -0,0 +1,149 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationData;
+
+/**
+ * Manager which handles high priority notifications that should "pulse" in when the device is
+ * dozing and/or in AOD. The pulse uses the notification's ambient view and pops in briefly
+ * before automatically dismissing the alert.
+ */
+public final class AmbientPulseManager extends AlertingNotificationManager {
+
+ protected final ArraySet<OnAmbientChangedListener> mListeners = new ArraySet<>();
+ @VisibleForTesting
+ protected long mExtensionTime;
+
+ public AmbientPulseManager(@NonNull final Context context) {
+ Resources resources = context.getResources();
+ mAutoDismissNotificationDecay = resources.getInteger(R.integer.ambient_notification_decay);
+ mMinimumDisplayTime = resources.getInteger(R.integer.ambient_notification_minimum_time);
+ mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
+ }
+
+ /**
+ * Adds an OnAmbientChangedListener to observe events.
+ */
+ public void addListener(@NonNull OnAmbientChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes the OnAmbientChangedListener from the observer list.
+ */
+ public void removeListener(@NonNull OnAmbientChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Extends the lifetime of the currently showing pulsing notification so that the pulse lasts
+ * longer.
+ */
+ public void extendPulse() {
+ AmbientEntry topEntry = getTopEntry();
+ if (topEntry == null) {
+ return;
+ }
+ topEntry.extendPulse();
+ }
+
+ @Override
+ protected void onAlertEntryAdded(AlertEntry alertEntry) {
+ NotificationData.Entry entry = alertEntry.mEntry;
+ entry.row.setAmbientPulsing(true);
+ for (OnAmbientChangedListener listener : mListeners) {
+ listener.onAmbientStateChanged(entry, true);
+ }
+ }
+
+ @Override
+ protected void onAlertEntryRemoved(AlertEntry alertEntry) {
+ NotificationData.Entry entry = alertEntry.mEntry;
+ entry.row.setAmbientPulsing(false);
+ for (OnAmbientChangedListener listener : mListeners) {
+ listener.onAmbientStateChanged(entry, false);
+ }
+ }
+
+ @Override
+ protected AlertEntry createAlertEntry() {
+ return new AmbientEntry();
+ }
+
+ /**
+ * Get the top pulsing entry. This should be the currently showing one if there are multiple.
+ * @return the currently showing entry
+ */
+ private AmbientEntry getTopEntry() {
+ if (mAlertEntries.isEmpty()) {
+ return null;
+ }
+ AlertEntry topEntry = null;
+ for (AlertEntry entry : mAlertEntries.values()) {
+ if (topEntry == null || entry.compareTo(topEntry) < 0) {
+ topEntry = entry;
+ }
+ }
+ return (AmbientEntry) topEntry;
+ }
+
+ /**
+ * Observer interface for any changes in the ambient entries.
+ */
+ public interface OnAmbientChangedListener {
+ /**
+ * Called when an entry starts or stops pulsing.
+ * @param entry the entry that changed
+ * @param isPulsing true if the entry is now pulsing, false otherwise
+ */
+ void onAmbientStateChanged(NotificationData.Entry entry, boolean isPulsing);
+ }
+
+ private final class AmbientEntry extends AlertEntry {
+ private boolean extended;
+
+ /**
+ * Extend the lifetime of the alertEntry so that it auto-removes later. Can only be
+ * extended once.
+ */
+ private void extendPulse() {
+ if (!extended) {
+ extended = true;
+ updateEntry(false);
+ }
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ extended = false;
+ }
+
+ @Override
+ protected long calculateFinishTime() {
+ return super.calculateFinishTime() + (extended ? mExtensionTime : 0);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index d479838..f69ad43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -321,7 +321,7 @@
&& !row.isLowPriority()));
}
- entry.row.setShowAmbient(mPresenter.isDozing());
+ entry.row.setOnAmbient(mPresenter.isDozing());
int userId = entry.notification.getUserId();
boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
entry.notification) && !entry.row.isRemoved();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
index 804e842..d097c8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -440,6 +440,8 @@
} else if (isHeadsUp) {
// Provide consistent ranking with headsUpManager
return mHeadsUpManager.compare(a, b);
+ } else if (a.row.isAmbientPulsing() != b.row.isAmbientPulsing()) {
+ return a.row.isAmbientPulsing() ? -1 : 1;
} else if (aMedia != bMedia) {
// Upsort current media notification.
return aMedia ? -1 : 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index ac01fa3..935eaac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -58,6 +58,8 @@
import com.android.systemui.UiOffloadThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
+import com.android.systemui.statusbar.AlertingNotificationManager;
+import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -91,7 +93,7 @@
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
private static final String TAG = "NotificationEntryMgr";
- protected static final boolean DEBUG = false;
+ protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
protected static final boolean ENABLE_HEADS_UP = true;
protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
@@ -121,6 +123,7 @@
Dependency.get(ForegroundServiceController.class);
protected final NotificationListener mNotificationListener =
Dependency.get(NotificationListener.class);
+ protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
protected IStatusBarService mBarService;
protected NotificationPresenter mPresenter;
@@ -264,6 +267,7 @@
}
mNotificationLifetimeExtenders.add(mHeadsUpManager);
+ mNotificationLifetimeExtenders.add(mAmbientPulseManager);
mNotificationLifetimeExtenders.add(mGutsManager);
mNotificationLifetimeExtenders.addAll(mRemoteInputManager.getLifetimeExtenders());
@@ -381,7 +385,7 @@
final int userId = n.getUserId();
try {
int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
- if (isHeadsUp(n.getKey())) {
+ if (mHeadsUpManager.isAlerting(n.getKey())) {
dismissalSurface = NotificationStats.DISMISSAL_PEEK;
} else if (mListContainer.hasPulsingNotifications()) {
dismissalSurface = NotificationStats.DISMISSAL_AOD;
@@ -432,12 +436,14 @@
}
private void addEntry(NotificationData.Entry shadeEntry) {
- boolean isHeadsUped = shouldPeek(shadeEntry);
- if (isHeadsUped) {
+ if (shouldHeadsUp(shadeEntry)) {
mHeadsUpManager.showNotification(shadeEntry);
// Mark as seen immediately
setNotificationShown(shadeEntry.notification);
}
+ if (shouldPulse(shadeEntry)) {
+ mAmbientPulseManager.showNotification(shadeEntry);
+ }
addNotificationViews(shadeEntry);
mCallback.onNotificationAdded(shadeEntry);
}
@@ -465,7 +471,11 @@
private void removeNotificationInternal(String key,
@Nullable NotificationListenerService.RankingMap ranking, boolean forceRemove) {
abortExistingInflation(key);
- if (mHeadsUpManager.contains(key)) {
+
+ // Attempt to remove notifications from their alert managers (heads up, ambient pulse).
+ // Though the remove itself may fail, it lets the manager know to remove as soon as
+ // possible.
+ if (mHeadsUpManager.isAlerting(key)) {
// A cancel() in response to a remote input shouldn't be delayed, as it makes the
// sending look longer than it takes.
// Also we should not defer the removal if reordering isn't allowed since otherwise
@@ -473,10 +483,11 @@
boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
&& !FORCE_REMOTE_INPUT_HISTORY
|| !mVisualStabilityManager.isReorderingAllowed();
-
- // Attempt to remove notification.
mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
}
+ if (mAmbientPulseManager.isAlerting(key)) {
+ mAmbientPulseManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+ }
NotificationData.Entry entry = mNotificationData.get(key);
@@ -651,13 +662,15 @@
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
- if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+ if (DEBUG) {
+ Log.d(TAG, "addNotification key=" + key);
+ }
mNotificationData.updateRanking(rankingMap);
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
NotificationData.Entry shadeEntry = createNotificationViews(notification, ranking);
- boolean isHeadsUped = shouldPeek(shadeEntry);
+ boolean isHeadsUped = shouldHeadsUp(shadeEntry);
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(shadeEntry)) {
if (DEBUG) {
@@ -750,7 +763,6 @@
extender.setShouldManageLifetime(entry, false /* shouldManage */);
}
- Notification n = notification.getNotification();
mNotificationData.updateRanking(ranking);
final StatusBarNotification oldNotification = entry.notification;
@@ -763,10 +775,12 @@
mForegroundServiceController.updateNotification(notification,
mNotificationData.getImportance(key));
- boolean shouldPeek = shouldPeek(entry, notification);
- boolean alertAgain = alertAgain(entry, n);
-
- updateHeadsUp(key, entry, shouldPeek, alertAgain);
+ boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
+ if (mPresenter.isDozing()) {
+ updateAlertState(entry, shouldPulse(entry), alertAgain, mAmbientPulseManager);
+ } else {
+ updateAlertState(entry, shouldHeadsUp(entry), alertAgain, mHeadsUpManager);
+ }
updateNotifications();
if (!notification.isClearable()) {
@@ -851,66 +865,147 @@
}
}
- protected boolean shouldPeek(NotificationData.Entry entry) {
- return shouldPeek(entry, entry.notification);
- }
+ /**
+ * Whether the notification should peek in from the top and alert the user.
+ *
+ * @param entry the entry to check
+ * @return true if the entry should heads up, false otherwise
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public boolean shouldHeadsUp(NotificationData.Entry entry) {
+ StatusBarNotification sbn = entry.notification;
- public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
- if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
- if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
+ if (mPresenter.isDozing()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey());
+ }
return false;
}
- if (mNotificationData.shouldFilterOut(entry)) {
- if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+ if (!canAlertCommon(entry)) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: no huns or vr mode");
+ }
return false;
}
boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
- if (!inUse && !mPresenter.isDozing()) {
+ if (!inUse) {
if (DEBUG) {
- Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+ Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
}
return false;
}
- if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(entry)) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- // Peeking triggers an ambient display pulse, so disable peek is ambient is active
- if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(entry)) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (entry.hasJustLaunchedFullScreenIntent()) {
- if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+ if (mNotificationData.shouldSuppressPeek(entry)) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+ }
return false;
}
if (isSnoozedPackage(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+ }
return false;
}
- // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
- int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
- : NotificationManager.IMPORTANCE_HIGH;
- if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
- if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+ if (entry.hasJustLaunchedFullScreenIntent()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+ }
return false;
}
- // Don't peek notifications that are suppressed due to group alert behavior
+ if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (!mCallback.canHeadsUp(entry, sbn)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Whether or not the notification should "pulse" on the user's display when the phone is
+ * dozing. This displays the ambient view of the notification.
+ *
+ * @param entry the entry to check
+ * @return true if the entry should ambient pulse, false otherwise
+ */
+ protected boolean shouldPulse(NotificationData.Entry entry) {
+ StatusBarNotification sbn = entry.notification;
+
+ if (!mPresenter.isDozing()) {
+ if (DEBUG) {
+ Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (!canAlertCommon(entry)) {
+ if (DEBUG) {
+ Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (mNotificationData.shouldSuppressAmbient(entry)) {
+ if (DEBUG) {
+ Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (mNotificationData.getImportance(sbn.getKey())
+ < NotificationManager.IMPORTANCE_DEFAULT) {
+ if (DEBUG) {
+ Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Common checks between heads up alerting and ambient pulse alerting. See
+ * {@link NotificationEntryManager#shouldHeadsUp(NotificationData.Entry)} and
+ * {@link NotificationEntryManager#shouldPulse(NotificationData.Entry)}. Notifications that
+ * fail any of these checks should not alert at all.
+ *
+ * @param entry the entry to check
+ * @return true if these checks pass, false if the notification should not alert
+ */
+ protected boolean canAlertCommon(NotificationData.Entry entry) {
+ StatusBarNotification sbn = entry.notification;
+
+ if (mNotificationData.shouldFilterOut(entry)) {
+ if (DEBUG) {
+ Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ // Don't alert notifications that are suppressed due to group alert behavior
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
- return false;
- }
-
- if (!mCallback.shouldPeek(entry, sbn)) {
+ if (DEBUG) {
+ Log.d(TAG, "No alerting: suppressed due to group alert behavior");
+ }
return false;
}
@@ -933,26 +1028,31 @@
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
}
- protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
- boolean alertAgain) {
- final boolean wasHeadsUp = isHeadsUp(key);
- if (wasHeadsUp) {
- if (!shouldPeek) {
+ /**
+ * Update the entry's alert state and call the appropriate {@link AlertingNotificationManager}
+ * method.
+ * @param entry entry to update
+ * @param shouldAlert whether or not it should be alerting
+ * @param alertAgain whether or not an alert should actually come in as if it were new
+ * @param alertManager the alerting notification manager that manages the alert state
+ */
+ private void updateAlertState(NotificationData.Entry entry, boolean shouldAlert,
+ boolean alertAgain, AlertingNotificationManager alertManager) {
+ final boolean wasAlerting = alertManager.isAlerting(entry.key);
+ if (wasAlerting) {
+ if (!shouldAlert) {
// We don't want this to be interrupting anymore, lets remove it
- mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+ alertManager.removeNotification(entry.key,
+ false /* ignoreEarliestRemovalTime */);
} else {
- mHeadsUpManager.updateNotification(entry.key, alertAgain);
+ alertManager.updateNotification(entry.key, alertAgain);
}
- } else if (shouldPeek && alertAgain) {
- // This notification was updated to be a heads-up, show it!
- mHeadsUpManager.showNotification(entry);
+ } else if (shouldAlert && alertAgain) {
+ // This notification was updated to be alerting, show it!
+ alertManager.showNotification(entry);
}
}
- protected boolean isHeadsUp(String key) {
- return mHeadsUpManager.contains(key);
- }
-
/**
* Callback for NotificationEntryManager.
*/
@@ -1008,12 +1108,12 @@
void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
/**
- * Returns true if NotificationEntryManager should peek this notification.
+ * Returns true if NotificationEntryManager can heads up this notification.
*
- * @param entry entry of the notification that might be peeked
- * @param sbn notification that might be peeked
- * @return true if the notification should be peeked
+ * @param entry entry of the notification that might be heads upped
+ * @param sbn notification that might be heads upped
+ * @return true if the notification can be heads upped
*/
- boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
+ boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 216ed68..019e88b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -174,6 +174,11 @@
*/
private boolean mOnKeyguard;
+ /**
+ * Whether or not the row is currently on the doze screen.
+ */
+ private boolean mOnAmbient;
+
private Animator mTranslateAnim;
private ArrayList<View> mTranslateableViews;
private NotificationContentView mPublicLayout;
@@ -186,7 +191,18 @@
private NotificationData.Entry mEntry;
private StatusBarNotification mStatusBarNotification;
private String mAppName;
+
+ /**
+ * Whether or not the notification is using the heads up view and should peek from the top.
+ */
private boolean mIsHeadsUp;
+
+ /**
+ * Whether or not the notification is using the ambient display view and is pulsing. This
+ * occurs when a high priority notification alerts while the phone is dozing or is on AOD.
+ */
+ private boolean mIsAmbientPulsing;
+
private boolean mLastChronometerRunning = true;
private ViewStub mChildrenContainerStub;
private NotificationGroupManager mGroupManager;
@@ -289,7 +305,6 @@
private float mContentTransformationAmount;
private boolean mIconsVisible = true;
private boolean mAboveShelf;
- private boolean mShowAmbient;
private boolean mIsLastChild;
private Runnable mOnDismissRunnable;
private boolean mIsLowPriority;
@@ -606,6 +621,14 @@
}
}
+ public boolean isAmbientPulsing() {
+ return mIsAmbientPulsing;
+ }
+
+ public void setAmbientPulsing(boolean isAmbientPulsing) {
+ mIsAmbientPulsing = isAmbientPulsing;
+ }
+
public void setGroupManager(NotificationGroupManager groupManager) {
mGroupManager = groupManager;
mPrivateLayout.setGroupManager(groupManager);
@@ -1854,7 +1877,7 @@
public void setDark(boolean dark, boolean fade, long delay) {
super.setDark(dark, fade, delay);
mDark = dark;
- if (!mIsHeadsUp) {
+ if (!mIsAmbientPulsing) {
// Only fade the showing view of the pulsing notification.
fade = false;
}
@@ -2155,7 +2178,7 @@
return mPrivateLayout.getMinHeight();
} else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
return getMinHeight();
- } else if (mIsSummaryWithChildren && (!mOnKeyguard || mShowAmbient)) {
+ } else if (mIsSummaryWithChildren && (!mOnKeyguard || mOnAmbient)) {
return mChildrenContainer.getIntrinsicHeight();
} else if (isHeadsUpAllowed() && (mIsHeadsUp || mHeadsupDisappearRunning)) {
if (isPinned() || mHeadsupDisappearRunning) {
@@ -2173,7 +2196,7 @@
}
private boolean isHeadsUpAllowed() {
- return !mOnKeyguard && !mShowAmbient;
+ return !mOnKeyguard && !mOnAmbient;
}
@Override
@@ -2818,11 +2841,11 @@
|| mExpandAnimationRunning || mChildIsExpanding);
}
- public void setShowAmbient(boolean showAmbient) {
- if (showAmbient != mShowAmbient) {
- mShowAmbient = showAmbient;
+ public void setOnAmbient(boolean onAmbient) {
+ if (onAmbient != mOnAmbient) {
+ mOnAmbient = onAmbient;
if (mChildrenContainer != null) {
- mChildrenContainer.notifyShowAmbientChanged();
+ mChildrenContainer.notifyDozingStateChanged();
}
notifyHeightChanged(false /* needsAnimation */);
}
@@ -2891,8 +2914,8 @@
return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f;
}
- public boolean isShowingAmbient() {
- return mShowAmbient;
+ public boolean isOnAmbient() {
+ return mOnAmbient;
}
public void setAboveShelf(boolean aboveShelf) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 0110610..4963a0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -725,7 +725,7 @@
}
public int getMaxHeight() {
- if (mContainingNotification.isShowingAmbient()) {
+ if (mContainingNotification.isOnAmbient()) {
return getShowingAmbientView().getHeight();
} else if (mExpandedChild != null) {
return getViewHeight(VISIBLE_TYPE_EXPANDED)
@@ -752,7 +752,7 @@
}
public int getMinHeight(boolean likeGroupExpanded) {
- if (mContainingNotification.isShowingAmbient()) {
+ if (mContainingNotification.isOnAmbient()) {
return getShowingAmbientView().getHeight();
} else if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
return getViewHeight(VISIBLE_TYPE_CONTRACTED);
@@ -1039,7 +1039,7 @@
* @return one of the static enum types in this view, calculated form the current state
*/
public int calculateVisibleType() {
- if (mContainingNotification.isShowingAmbient()) {
+ if (mContainingNotification.isOnAmbient()) {
if (mIsChildInGroup && mAmbientSingleLineChild != null) {
return VISIBLE_TYPE_AMBIENT_SINGLELINE;
} else if (mAmbientChild != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 15eaaac..8969aca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -20,14 +20,15 @@
import android.content.Context;
import android.view.View;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
@@ -44,7 +45,7 @@
private int mSpeedBumpIndex = -1;
private boolean mDark;
private boolean mHideSensitive;
- private HeadsUpManager mHeadsUpManager;
+ private AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
private float mStackTranslation;
private int mLayoutHeight;
private int mTopPadding;
@@ -207,10 +208,6 @@
mSpeedBumpIndex = shelfIndex;
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
public float getStackTranslation() {
return mStackTranslation;
}
@@ -334,10 +331,10 @@
}
public boolean isPulsing(NotificationData.Entry entry) {
- if (!mPulsing || mHeadsUpManager == null) {
+ if (!mPulsing || mAmbientPulseManager == null) {
return false;
}
- return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry));
+ return mAmbientPulseManager.isAlerting(entry.key);
}
public boolean isPanelTracking() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 3d44e3c..da089b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -213,7 +213,7 @@
// calculated correctly as they are used to calculate how many we can fit on the screen.
boolean isOverflow = i == overflowIndex;
child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null &&
- !mContainingNotification.isShowingAmbient()
+ !mContainingNotification.isOnAmbient()
? mOverflowNumber.getMeasuredWidth() : 0);
child.measure(widthMeasureSpec, newHeightSpec);
// layout the divider
@@ -406,7 +406,7 @@
if (childCount > maxAllowedVisibleChildren) {
int number = childCount - maxAllowedVisibleChildren;
mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number);
- if (mContainingNotification.isShowingAmbient()) {
+ if (mContainingNotification.isOnAmbient()) {
ExpandableNotificationRow overflowView = mChildren.get(0);
HybridNotificationView ambientSingleLineView = overflowView == null ? null
: overflowView.getAmbientSingleLineView();
@@ -522,7 +522,7 @@
if (mUserLocked) {
expandFactor = getGroupExpandFraction();
}
- boolean childrenExpanded = mChildrenExpanded || mContainingNotification.isShowingAmbient();
+ boolean childrenExpanded = mChildrenExpanded || mContainingNotification.isOnAmbient();
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
@@ -641,7 +641,7 @@
getMaxAllowedVisibleChildren(true /* likeCollapsed */), childCount) - 1);
mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView));
- if (mContainingNotification.isShowingAmbient()) {
+ if (mContainingNotification.isOnAmbient()) {
mGroupOverFlowState.alpha = 0.0f;
} else if (!mChildrenExpanded) {
HybridNotificationView alignView = overflowView.getSingleLineView();
@@ -710,7 +710,7 @@
@VisibleForTesting
int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
- if (mContainingNotification.isShowingAmbient()) {
+ if (mContainingNotification.isOnAmbient()) {
return NUMBER_OF_CHILDREN_WHEN_AMBIENT;
}
if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())
@@ -900,7 +900,7 @@
return mCurrentHeader;
}
- public void notifyShowAmbientChanged() {
+ public void notifyDozingStateChanged() {
updateHeaderVisibility(false);
updateGroupOverflow();
}
@@ -970,7 +970,7 @@
private ViewGroup calculateDesiredHeader() {
ViewGroup desiredHeader;
- if (mContainingNotification.isShowingAmbient()) {
+ if (mContainingNotification.isOnAmbient()) {
desiredHeader = mNotificationHeaderAmbient;
} else if (showingAsLowPriority()) {
desiredHeader = mNotificationHeaderLowPriority;
@@ -1126,7 +1126,7 @@
}
public int getMinHeight() {
- return getMinHeight(mContainingNotification.isShowingAmbient()
+ return getMinHeight(mContainingNotification.isOnAmbient()
? NUMBER_OF_CHILDREN_WHEN_AMBIENT
: NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index da98565..958a162 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4828,7 +4828,6 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
- mAmbientState.setHeadsUpManager(headsUpManager);
mHeadsUpManager.addListener(mRoundnessManager);
mHeadsUpManager.setAnimationStateHandler(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 0d3ba77..25db4f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -63,9 +63,14 @@
if (!mDozing) {
return;
}
- mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
- mHandler.postDelayed(mPulseOutExtended,
- mDozeParameters.getPulseVisibleDurationExtended());
+ // All pulses except notifications should time out on their own. Pulses due to
+ // notifications should instead be managed externally based off the notification's
+ // lifetime.
+ if (mPulseReason != DozeLog.PULSE_REASON_NOTIFICATION) {
+ mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+ mHandler.postDelayed(mPulseOutExtended,
+ mDozeParameters.getPulseVisibleDurationExtended());
+ }
mFullyPulsing = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4a05989..cfc3271 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -177,7 +177,7 @@
mReleaseOnExpandFinish = false;
} else {
for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- if (contains(entry.key)) {
+ if (isAlerting(entry.key)) {
// Maybe the heads-up was removed already
removeAlertEntry(entry.key);
}
@@ -345,7 +345,7 @@
public void onReorderingAllowed() {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (contains(entry.key)) {
+ if (isAlerting(entry.key)) {
// Maybe the heads-up was removed already
removeAlertEntry(entry.key);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 6b6566c..c08366a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.annotation.NonNull;
import android.app.Notification;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
@@ -23,6 +24,9 @@
import android.util.Log;
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;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -43,15 +47,18 @@
/**
* A class to handle notifications and their corresponding groups.
*/
-public class NotificationGroupManager implements OnHeadsUpChangedListener {
+public class NotificationGroupManager implements OnHeadsUpChangedListener,
+ OnAmbientChangedListener {
private static final String TAG = "NotificationGroupManager";
- private static final long HEADS_UP_TRANSFER_TIMEOUT = 300;
+ private static final long ALERT_TRANSFER_TIMEOUT = 300;
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private OnGroupChangeListener mListener;
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;
@@ -162,40 +169,58 @@
mListener.onGroupCreatedFromChildren(group);
}
}
- cleanUpHeadsUpStatesOnAdd(group, false /* addIsPending */);
+ cleanUpAlertStatesOnAdd(group, false /* addIsPending */);
}
public void onPendingEntryAdded(NotificationData.Entry shadeEntry) {
String groupKey = getGroupKey(shadeEntry.notification);
NotificationGroup group = mGroupMap.get(groupKey);
if (group != null) {
- cleanUpHeadsUpStatesOnAdd(group, true /* addIsPending */);
+ cleanUpAlertStatesOnAdd(group, true /* addIsPending */);
}
}
/**
- * Clean up the heads up states when a new child was added.
+ * 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.
+ */
+ 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 cleanUpHeadsUpStatesOnAdd(NotificationGroup group, boolean addIsPending) {
- if (!addIsPending && group.hunSummaryOnNextAddition) {
- if (!mHeadsUpManager.contains(group.summary.key)) {
- mHeadsUpManager.showNotification(group.summary);
+ 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.hunSummaryOnNextAddition = false;
+ 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 heads up to the
- // child, otherwise the invisible summary would be heads-upped.
+ // 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 heads up to the old child and the wrong notification
- // would be heads-upped. In oder to avoid this, we'll recover from this issue and hun the
+ // 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.lastHeadsUpTransfer < HEADS_UP_TRANSFER_TIMEOUT) {
+ if (SystemClock.elapsedRealtime() - group.lastAlertTransfer < ALERT_TRANSFER_TIMEOUT) {
if (!onlySummaryAlerts(group.summary)) {
return;
}
@@ -215,26 +240,24 @@
int size = children.size();
for (int i = 0; i < size; i++) {
NotificationData.Entry entry = children.get(i);
- if (onlySummaryAlerts(entry) && entry.row.isHeadsUp()) {
+ if (onlySummaryAlerts(entry) && alertManager.isAlerting(entry.key)) {
releasedChild = true;
- mHeadsUpManager.removeNotification(
- entry.key, true /* releaseImmediately */);
+ alertManager.removeNotification(entry.key, true /* releaseImmediately */);
}
}
if (isolatedChild != null && onlySummaryAlerts(isolatedChild)
- && isolatedChild.row.isHeadsUp()) {
+ && alertManager.isAlerting(isolatedChild.key)) {
releasedChild = true;
- mHeadsUpManager.removeNotification(
- isolatedChild.key, true /* releaseImmediately */);
+ alertManager.removeNotification(isolatedChild.key, true /* releaseImmediately */);
}
- if (releasedChild && !mHeadsUpManager.contains(group.summary.key)) {
+ if (releasedChild && !alertManager.isAlerting(group.summary.key)) {
boolean notifyImmediately = (numChildren - numPendingChildren) > 1;
if (notifyImmediately) {
- mHeadsUpManager.showNotification(group.summary);
+ alertManager.showNotification(group.summary);
} else {
- group.hunSummaryOnNextAddition = true;
+ group.alertSummaryOnNextAddition = true;
}
- group.lastHeadsUpTransfer = 0;
+ group.lastAlertTransfer = 0;
}
}
}
@@ -264,8 +287,8 @@
}
private void onEntryBecomingChild(NotificationData.Entry entry) {
- if (entry.row.isHeadsUp()) {
- onHeadsUpStateChanged(entry, true);
+ if (shouldIsolate(entry)) {
+ isolateNotification(entry);
}
}
@@ -281,7 +304,11 @@
&& hasIsolatedChildren(group)));
if (prevSuppressed != group.suppressed) {
if (group.suppressed) {
- handleSuppressedSummaryHeadsUpped(group.summary);
+ if (mHeadsUpManager.isAlerting(group.summary.key)) {
+ handleSuppressedSummaryAlerted(group.summary, mHeadsUpManager);
+ } else if (mAmbientPulseManager.isAlerting(group.summary.key)) {
+ handleSuppressedSummaryAlerted(group.summary, mAmbientPulseManager);
+ }
}
if (!mIsUpdatingUnchangedGroup && mListener != null) {
mListener.onGroupsChanged();
@@ -495,54 +522,56 @@
}
@Override
+ public void onAmbientStateChanged(NotificationData.Entry entry, boolean isAmbient) {
+ onAlertStateChanged(entry, isAmbient, mAmbientPulseManager);
+ }
+
+ @Override
public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
+ }
+
+ private void onAlertStateChanged(NotificationData.Entry entry, boolean isAlerting,
+ AlertingNotificationManager alertManager) {
final StatusBarNotification sbn = entry.notification;
- if (entry.row.isHeadsUp()) {
- if (shouldIsolate(sbn)) {
- // We will be isolated now, so lets update the groups
- onEntryRemovedInternal(entry, entry.notification);
-
- mIsolatedEntries.put(sbn.getKey(), sbn);
-
- onEntryAdded(entry);
- // We also need to update the suppression of the old group, because this call comes
- // even before the groupManager knows about the notification at all.
- // When the notification gets added afterwards it is already isolated and therefore
- // it doesn't lead to an update.
- updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
- mListener.onGroupsChanged();
- } else {
- handleSuppressedSummaryHeadsUpped(entry);
+ if (isAlerting) {
+ if (shouldIsolate(entry)) {
+ isolateNotification(entry);
+ } else if (sbn.getNotification().isGroupSummary()
+ && isGroupSuppressed(sbn.getGroupKey())){
+ handleSuppressedSummaryAlerted(entry, alertManager);
}
} else {
- if (mIsolatedEntries.containsKey(sbn.getKey())) {
- // not isolated anymore, we need to update the groups
- onEntryRemovedInternal(entry, entry.notification);
- mIsolatedEntries.remove(sbn.getKey());
- onEntryAdded(entry);
- mListener.onGroupsChanged();
- }
+ stopIsolatingNotification(entry);
}
}
- private void handleSuppressedSummaryHeadsUpped(NotificationData.Entry entry) {
- StatusBarNotification sbn = entry.notification;
+ /**
+ * 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()
- || !entry.row.isHeadsUp()) {
+ || !alertManager.isAlerting(sbn.getKey())) {
return;
}
- // The parent of a suppressed group got huned, lets hun the child!
+ // The parent of a suppressed group got alerted, lets alert the child!
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
- if (pendingInflationsWillAddChildren(notificationGroup)) {
- // New children will actually be added to this group, let's not transfer the heads
- // up
- return;
- }
-
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;
@@ -551,20 +580,35 @@
}
if (child != null) {
if (child.row.keepInParent() || child.row.isRemoved() || child.row.isDismissed()) {
- // the notification is actually already removed, no need to do heads-up on it.
+ // the notification is actually already removed, no need to do alert on it.
return;
}
- if (mHeadsUpManager.contains(child.key)) {
- mHeadsUpManager.updateNotification(child.key, true /* alert */);
- } else {
- if (onlySummaryAlerts(entry)) {
- notificationGroup.lastHeadsUpTransfer = SystemClock.elapsedRealtime();
- }
- mHeadsUpManager.showNotification(child);
- }
+ transferAlertStateToChild(summary, child, alertManager);
}
}
- mHeadsUpManager.removeNotification(entry.key, true /* releaseImmediately */);
+ }
+
+ /**
+ * 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) {
@@ -596,13 +640,69 @@
return false;
}
- private boolean shouldIsolate(StatusBarNotification sbn) {
+ /**
+ * Whether a notification that is normally part of a group should be temporarily isolated from
+ * the group and put in their own group visually. This generally happens when the notification
+ * is alerting.
+ *
+ * @param entry the notification to check
+ * @return true if the entry should be isolated
+ */
+
+ private boolean shouldIsolate(NotificationData.Entry entry) {
+ StatusBarNotification sbn = entry.notification;
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
- return (sbn.isGroup() && !sbn.getNotification().isGroupSummary())
- && (sbn.getNotification().fullScreenIntent != null
- || notificationGroup == null
- || !notificationGroup.expanded
- || isGroupNotFullyVisible(notificationGroup));
+ if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
+ return false;
+ }
+ if (!mIsDozing && !mHeadsUpManager.isAlerting(entry.key)) {
+ return false;
+ }
+ if (mIsDozing && !mAmbientPulseManager.isAlerting(entry.key)) {
+ return false;
+ }
+ return (sbn.getNotification().fullScreenIntent != null
+ || notificationGroup == null
+ || !notificationGroup.expanded
+ || isGroupNotFullyVisible(notificationGroup));
+ }
+
+ /**
+ * Isolate a notification from its group so that it visually shows as its own group.
+ *
+ * @param entry the notification to isolate
+ */
+ private void isolateNotification(NotificationData.Entry entry) {
+ StatusBarNotification sbn = entry.notification;
+
+ // We will be isolated now, so lets update the groups
+ onEntryRemovedInternal(entry, entry.notification);
+
+ mIsolatedEntries.put(sbn.getKey(), sbn);
+
+ onEntryAdded(entry);
+ // We also need to update the suppression of the old group, because this call comes
+ // even before the groupManager knows about the notification at all.
+ // When the notification gets added afterwards it is already isolated and therefore
+ // it doesn't lead to an update.
+ updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
+ mListener.onGroupsChanged();
+ }
+
+ /**
+ * Stop isolating a notification and re-group it with its original logical group.
+ *
+ * @param entry the notification to un-isolate
+ */
+ private void stopIsolatingNotification(NotificationData.Entry entry) {
+ StatusBarNotification sbn = entry.notification;
+ if (mIsolatedEntries.containsKey(sbn.getKey())) {
+ // not isolated anymore, we need to update the groups
+ onEntryRemovedInternal(entry, entry.notification);
+ mIsolatedEntries.remove(sbn.getKey());
+ onEntryAdded(entry);
+ mListener.onGroupsChanged();
+ }
}
private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) {
@@ -641,11 +741,11 @@
*/
public boolean suppressed;
/**
- * The time when the last heads transfer from group to child happened, while the summary
- * has the flags to heads up on its own.
+ * 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 lastHeadsUpTransfer;
- public boolean hunSummaryOnNextAddition;
+ public long lastAlertTransfer;
+ public boolean alertSummaryOnNextAddition;
@Override
public String toString() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 759e8ea..e1d8638 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -187,6 +187,7 @@
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.AppOpsListener;
import com.android.systemui.statusbar.BackDropView;
@@ -254,7 +255,7 @@
ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter,
- StatusBarStateController.StateListener {
+ StatusBarStateController.StateListener, AmbientPulseManager.OnAmbientChangedListener {
public static final boolean MULTIUSER_DEBUG = false;
public static final boolean ENABLE_CHILD_NOTIFICATIONS
@@ -865,6 +866,8 @@
mHeadsUpManager.addListener(mNotificationPanel);
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mVisualStabilityManager);
+ mAmbientPulseManager.addListener(this);
+ mAmbientPulseManager.addListener(mGroupManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
putComponent(HeadsUpManager.class, mHeadsUpManager);
@@ -1233,7 +1236,7 @@
@Override
public void onPerformRemoveNotification(StatusBarNotification n) {
if (mNotificationPanel.hasPulsingNotifications() &&
- !mHeadsUpManager.hasNotifications()) {
+ !mAmbientPulseManager.hasNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
mDozeScrimController.pulseOutNow();
@@ -1697,8 +1700,12 @@
}
@Override
- public boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
- if (mIsOccluded && !isDozing()) {
+ public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) {
+ if (isDozing()) {
+ return false;
+ }
+
+ if (mIsOccluded) {
boolean devicePublic = mLockscreenUserManager.
isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
boolean userPublic = devicePublic
@@ -1711,17 +1718,14 @@
if (!panelsEnabled()) {
if (DEBUG) {
- Log.d(TAG, "No peeking: disabled panel : " + sbn.getKey());
+ Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
}
return false;
}
if (sbn.getNotification().fullScreenIntent != null) {
if (mAccessibilityManager.isTouchExplorationEnabled()) {
- if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
- return false;
- } else if (isDozing()) {
- // We never want heads up when we are dozing.
+ if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
return false;
} else {
// we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
@@ -1797,9 +1801,16 @@
@Override
public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
mEntryManager.updateNotificationRanking(null /* rankingMap */);
+ }
- if (isHeadsUp) {
- mDozeServiceHost.fireNotificationHeadsUp();
+ @Override
+ public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
+ mEntryManager.updateNotificationRanking(null);
+ if (isAmbient) {
+ mDozeServiceHost.fireNotificationPulse();
+ } else if (!mAmbientPulseManager.hasNotifications()) {
+ // There are no longer any notifications to show. We should end the pulse now.
+ mDozeScrimController.pulseOutNow();
}
}
@@ -3603,6 +3614,7 @@
mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation);
mNotificationLogger.setDozing(mDozing);
+ mGroupManager.setDozing(mDozing);
updateQsExpansionEnabled();
Trace.endSection();
}
@@ -4149,6 +4161,7 @@
@Override
public void onStartedWakingUp() {
mDeviceInteractive = true;
+ mAmbientPulseManager.releaseAllImmediately();
mVisualStabilityManager.setScreenOn(true);
mNotificationPanel.setTouchAndAnimationDisabled(false);
mDozeServiceHost.stopDozing();
@@ -4181,11 +4194,7 @@
public void onScreenTurnedOff() {
mFalsingManager.onScreenOff();
mScrimController.onScreenTurnedOff();
- // If we pulse in from AOD, we turn the screen off first. However, updatingIsKeyguard
- // in that case destroys the HeadsUpManager state, so don't do it in that case.
- if (!isPulsing()) {
- updateIsKeyguard();
- }
+ updateIsKeyguard();
}
};
@@ -4424,9 +4433,9 @@
}
}
- public void fireNotificationHeadsUp() {
+ public void fireNotificationPulse() {
for (Callback callback : mCallbacks) {
- callback.onNotificationHeadsUp();
+ callback.onNotificationAlerted();
}
}
@@ -4462,7 +4471,7 @@
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- if (mHeadsUpManager.hasNotifications()) {
+ if (mAmbientPulseManager.hasNotifications()) {
// Only pulse the stack scroller if there's actually something to show.
// Otherwise just show the always-on screen.
setPulsing(true);
@@ -4543,7 +4552,11 @@
@Override
public void extendPulse() {
- mDozeScrimController.extendPulse();
+ if (mDozeScrimController.isPulsing() && mAmbientPulseManager.hasNotifications()) {
+ mAmbientPulseManager.extendPulse();
+ } else {
+ mDozeScrimController.extendPulse();
+ }
}
@Override
@@ -4629,6 +4642,8 @@
// for heads up notifications
protected HeadsUpManagerPhone mHeadsUpManager;
+ protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
+
private AboveShelfObserver mAboveShelfObserver;
// handling reordering
@@ -4734,7 +4749,7 @@
final boolean wasOccluded = mIsOccluded;
dismissKeyguardThenExecute(() -> {
// TODO: Some of this code may be able to move to NotificationEntryManager.
- if (mHeadsUpManager != null && mHeadsUpManager.contains(notificationKey)) {
+ if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(notificationKey)) {
// Release the HUN notification to the shade.
if (isPresenterFullyCollapsed()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index c2da7f5..5c8336c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -88,14 +88,14 @@
mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
- mHost.callback.onNotificationHeadsUp();
+ mHost.callback.onNotificationAlerted();
mSensors.getMockProximitySensor().sendProximityResult(false); /* Near */
verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt());
- mHost.callback.onNotificationHeadsUp();
+ mHost.callback.onNotificationAlerted();
mSensors.getMockProximitySensor().sendProximityResult(true); /* Far */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index f21ce27..8b41516 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -56,19 +56,19 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- private static final int TEST_MINIMUM_DISPLAY_TIME = 200;
- private static final int TEST_AUTO_DISMISS_TIME = 500;
+ protected static final int TEST_MINIMUM_DISPLAY_TIME = 200;
+ protected static final int TEST_AUTO_DISMISS_TIME = 500;
// Number of notifications to use in tests requiring multiple notifications
private static final int TEST_NUM_NOTIFICATIONS = 4;
- private static final int TEST_TIMEOUT_TIME = 10000;
- private final Runnable TEST_TIMEOUT_RUNNABLE = () -> mTimedOut = true;
+ protected static final int TEST_TIMEOUT_TIME = 10000;
+ protected final Runnable TEST_TIMEOUT_RUNNABLE = () -> mTimedOut = true;
private AlertingNotificationManager mAlertingNotificationManager;
protected NotificationData.Entry mEntry;
protected Handler mTestHandler;
private StatusBarNotification mSbn;
- private boolean mTimedOut = false;
+ protected boolean mTimedOut = false;
@Mock protected ExpandableNotificationRow mRow;
@@ -122,7 +122,7 @@
public void testShowNotification_addsEntry() {
mAlertingNotificationManager.showNotification(mEntry);
- assertTrue(mAlertingNotificationManager.contains(mEntry.key));
+ assertTrue(mAlertingNotificationManager.isAlerting(mEntry.key));
assertTrue(mAlertingNotificationManager.hasNotifications());
assertEquals(mEntry, mAlertingNotificationManager.getEntry(mEntry.key));
}
@@ -136,7 +136,7 @@
TestableLooper.get(this).processMessages(1);
assertFalse("Test timed out", mTimedOut);
- assertFalse(mAlertingNotificationManager.contains(mEntry.key));
+ assertFalse(mAlertingNotificationManager.isAlerting(mEntry.key));
}
@Test
@@ -146,7 +146,7 @@
// Try to remove but defer, since the notification has not been shown long enough.
mAlertingNotificationManager.removeNotification(mEntry.key, false /* releaseImmediately */);
- assertTrue(mAlertingNotificationManager.contains(mEntry.key));
+ assertTrue(mAlertingNotificationManager.isAlerting(mEntry.key));
}
@Test
@@ -156,7 +156,7 @@
// Remove forcibly with releaseImmediately = true.
mAlertingNotificationManager.removeNotification(mEntry.key, true /* releaseImmediately */);
- assertFalse(mAlertingNotificationManager.contains(mEntry.key));
+ assertFalse(mAlertingNotificationManager.isAlerting(mEntry.key));
}
@Test
@@ -174,10 +174,18 @@
}
@Test
- public void testShouldExtendLifetime_notShownLongEnough() {
+ public void testCanRemoveImmediately_notShownLongEnough() {
mAlertingNotificationManager.showNotification(mEntry);
- // The entry has just been added so the lifetime should be extended
+ // The entry has just been added so we should not remove immediately.
+ assertFalse(mAlertingNotificationManager.canRemoveImmediately(mEntry.key));
+ }
+
+ @Test
+ public void testShouldExtendLifetime() {
+ mAlertingNotificationManager.showNotification(mEntry);
+
+ // While the entry is alerting, it should not be removable.
assertTrue(mAlertingNotificationManager.shouldExtendLifetime(mEntry));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AmbientPulseManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AmbientPulseManagerTest.java
new file mode 100644
index 0000000..f0344e6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AmbientPulseManagerTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AmbientPulseManagerTest extends AlertingNotificationManagerTest {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ private static final int TEST_EXTENSION_TIME = 500;
+ private AmbientPulseManager mAmbientPulseManager;
+ private boolean mLivesPastNormalTime;
+
+ protected AlertingNotificationManager createAlertingNotificationManager() {
+ return mAmbientPulseManager;
+ }
+
+ @Before
+ public void setUp() {
+ mAmbientPulseManager = new AmbientPulseManager(mContext);
+ mAmbientPulseManager.mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
+ mAmbientPulseManager.mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+ mAmbientPulseManager.mExtensionTime = TEST_EXTENSION_TIME;
+ super.setUp();
+ mAmbientPulseManager.mHandler = mTestHandler;
+ }
+
+ @Test
+ public void testExtendPulse() {
+ mAmbientPulseManager.showNotification(mEntry);
+ Runnable pastNormalTimeRunnable =
+ () -> mLivesPastNormalTime = mAmbientPulseManager.isAlerting(mEntry.key);
+ mTestHandler.postDelayed(pastNormalTimeRunnable,
+ mAmbientPulseManager.mAutoDismissNotificationDecay +
+ mAmbientPulseManager.mExtensionTime / 2);
+ mTestHandler.postDelayed(TEST_TIMEOUT_RUNNABLE, TEST_TIMEOUT_TIME);
+
+ mAmbientPulseManager.extendPulse();
+
+ // Wait for normal time runnable and extended remove runnable and process them on arrival.
+ TestableLooper.get(this).processMessages(2);
+
+ assertFalse("Test timed out", mTimedOut);
+ assertTrue("Pulse was not extended", mLivesPastNormalTime);
+ assertFalse(mAmbientPulseManager.isAlerting(mEntry.key));
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index b2170fa..edf29ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -31,6 +31,7 @@
import android.view.LayoutInflater;
import android.widget.RemoteViews;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.NotificationInflaterTest;
@@ -63,6 +64,7 @@
mContext = context;
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null);
+ mGroupManager.setHeadsUpManager(mHeadsUpManager);
}
public ExpandableNotificationRow createRow() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 087aa59..2728453 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.stack;
import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.NotificationHeaderView;
@@ -51,7 +50,7 @@
@Test
public void testGetMaxAllowedVisibleChildren_ambient() {
- mGroup.setShowAmbient(true);
+ mGroup.setOnAmbient(true);
Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_AMBIENT);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index a81d17f..1070795 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -82,28 +82,26 @@
// Remove should succeed because the notification is swiped out
mHeadsUpManager.removeNotification(mEntry.key, false /* releaseImmediately */);
- assertFalse(mHeadsUpManager.contains(mEntry.key));
+ assertFalse(mHeadsUpManager.isAlerting(mEntry.key));
}
@Test
- public void testShouldExtendLifetime_swipedOut() {
+ public void testCanRemoveImmediately_swipedOut() {
mHeadsUpManager.showNotification(mEntry);
mHeadsUpManager.addSwipedOutNotification(mEntry.key);
- // Notification is swiped so its lifetime should not be extended even if it hasn't been
- // shown long enough
- assertFalse(mHeadsUpManager.shouldExtendLifetime(mEntry));
+ // Notification is swiped so it can be immediately removed.
+ assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.key));
}
@Test
- public void testShouldExtendLifetime_notTopEntry() {
+ public void testCanRemoveImmediately_notTopEntry() {
NotificationData.Entry laterEntry = new NotificationData.Entry(createNewNotification(1));
laterEntry.row = mRow;
mHeadsUpManager.showNotification(mEntry);
mHeadsUpManager.showNotification(laterEntry);
- // Notification is "behind" a higher priority notification so we have no reason to keep
- // its lifetime extended
- assertFalse(mHeadsUpManager.shouldExtendLifetime(mEntry));
+ // Notification is "behind" a higher priority notification so we can remove it immediately.
+ assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.key));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
index 6a3c8a8..464f74b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
@@ -36,6 +36,7 @@
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;
@@ -60,15 +61,23 @@
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 = new NotificationGroupManager();
+ private NotificationGroupManager mGroupManager;
private int mId = 0;
@Mock HeadsUpManager mHeadsUpManager;
+ @Mock AmbientPulseManager mAmbientPulseManager;
@Before
public void setup() {
- mGroupManager.setHeadsUpManager(mHeadsUpManager);
- mGroupManager.setOnGroupChangeListener(mock(OnGroupChangeListener.class));
+ mDependency.injectTestDependency(AmbientPulseManager.class, mAmbientPulseManager);
+
+ initializeGroupManager();
+ }
+
+ private void initializeGroupManager() {
+ mGroupManager = new NotificationGroupManager();
+ mGroupManager.setHeadsUpManager(mHeadsUpManager);
+ mGroupManager.setOnGroupChangeListener(mock(OnGroupChangeListener.class));
}
@Test
@@ -141,8 +150,7 @@
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(createChildNotification());
- when(childEntry.row.isHeadsUp()).thenReturn(true);
- when(mHeadsUpManager.contains(childEntry.key)).thenReturn(true);
+ when(mHeadsUpManager.isAlerting(childEntry.key)).thenReturn(true);
mGroupManager.onHeadsUpStateChanged(childEntry, true);
@@ -154,17 +162,35 @@
}
@Test
+ public void testAmbientPulseEntryIsIsolated() {
+ mGroupManager.setDozing(true);
+ NotificationData.Entry childEntry = createChildNotification();
+ NotificationData.Entry summaryEntry = createSummaryNotification();
+ mGroupManager.onEntryAdded(summaryEntry);
+ mGroupManager.onEntryAdded(childEntry);
+ mGroupManager.onEntryAdded(createChildNotification());
+ when(mAmbientPulseManager.isAlerting(childEntry.key)).thenReturn(true);
+
+ mGroupManager.onAmbientStateChanged(childEntry, true);
+
+ // Child entries that are heads upped should be considered separate groups visually even if
+ // they are the same group logically
+ assertEquals(childEntry.row, mGroupManager.getGroupSummary(childEntry.notification));
+ assertEquals(summaryEntry.row,
+ mGroupManager.getLogicalGroupSummary(childEntry.notification));
+ }
+
+ @Test
public void testSuppressedSummaryHeadsUpTransfersToChild() {
NotificationData.Entry summaryEntry = createSummaryNotification();
- when(summaryEntry.row.isHeadsUp()).thenReturn(true);
- when(mHeadsUpManager.contains(summaryEntry.key)).thenReturn(true);
+ when(mHeadsUpManager.isAlerting(summaryEntry.key)).thenReturn(true);
NotificationData.Entry childEntry = createChildNotification();
- // Summary will be suppressed because there is only one child
+ // 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
+ // A suppressed summary should transfer its heads up state to the child.
verify(mHeadsUpManager, never()).showNotification(summaryEntry);
verify(mHeadsUpManager).showNotification(childEntry);
}
@@ -175,24 +201,64 @@
mGroupManager.setHeadsUpManager(mHeadsUpManager);
NotificationData.Entry summaryEntry =
createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- when(summaryEntry.row.isHeadsUp()).thenReturn(true);
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);
- when(summaryEntry.row.isHeadsUp()).thenReturn(false);
- when(childEntry.row.isHeadsUp()).thenReturn(true);
// 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.contains(summaryEntry.key));
- assertFalse(mHeadsUpManager.contains(childEntry.key));
+ 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() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index cbba251..5006b0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -358,7 +358,7 @@
}
@Test
- public void testShouldPeek_nonSuppressedGroupSummary() {
+ public void testShouldHeadsUp_nonSuppressedGroupSummary() {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
when(mNotificationData.shouldSuppressStatusBar(any())).thenReturn(false);
@@ -375,11 +375,11 @@
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertTrue(mEntryManager.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldHeadsUp(entry));
}
@Test
- public void testShouldPeek_suppressedGroupSummary() {
+ public void testShouldHeadsUp_suppressedGroupSummary() {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
when(mNotificationData.shouldSuppressStatusBar(any())).thenReturn(false);
@@ -396,11 +396,11 @@
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertFalse(mEntryManager.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldHeadsUp(entry));
}
@Test
- public void testShouldPeek_suppressedPeek() {
+ public void testShouldHeadsUp_suppressedHeadsUp() {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
when(mNotificationData.shouldFilterOut(any())).thenReturn(false);
@@ -414,11 +414,11 @@
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertFalse(mEntryManager.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldHeadsUp(entry));
}
@Test
- public void testShouldPeek_noSuppressedPeek() {
+ public void testShouldHeadsUp_noSuppressedHeadsUp() {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
when(mNotificationData.shouldFilterOut(any())).thenReturn(false);
@@ -432,31 +432,31 @@
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertTrue(mEntryManager.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldHeadsUp(entry));
}
@Test
- public void testPeek_disabledStatusBar() {
+ public void testHeadsUp_disabledStatusBar() {
Notification n = new Notification.Builder(getContext(), "a").build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
mStatusBar.disable(StatusBarManager.DISABLE_EXPAND, 0, false /* animate */);
- assertFalse("The panel shouldn't allow peek while disabled",
- mStatusBar.shouldPeek(entry, sbn));
+ assertFalse("The panel shouldn't allow heads up while disabled",
+ mStatusBar.canHeadsUp(entry, sbn));
}
@Test
- public void testPeek_disabledNotificationShade() {
+ public void testHeadsUp_disabledNotificationShade() {
Notification n = new Notification.Builder(getContext(), "a").build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
mStatusBar.disable(0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false /* animate */);
- assertFalse("The panel shouldn't allow peek while notitifcation shade disabled",
- mStatusBar.shouldPeek(entry, sbn));
+ assertFalse("The panel shouldn't allow heads up while notification shade disabled",
+ mStatusBar.canHeadsUp(entry, sbn));
}
@Test
@@ -472,7 +472,7 @@
}
@Test
- public void testPanelOpenForPeek() {
+ public void testPanelOpenForHeadsUp() {
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
when(mNotificationList.size()).thenReturn(5);