Loading packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java 0 → 100644 +317 −0 Original line number Diff line number Diff line /* * 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.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.statusbar.notification.NotificationData; import java.util.stream.Stream; /** * A manager which contains notification alerting functionality, providing methods to add and * remove notifications that appear on screen for a period of time and dismiss themselves at the * appropriate time. These include heads up notifications and ambient pulses. */ public abstract class AlertingNotificationManager { private static final String TAG = "AlertNotifManager"; protected final Clock mClock = new Clock(); protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>(); protected int mMinimumDisplayTime; protected int mAutoDismissNotificationDecay; @VisibleForTesting public Handler mHandler = new Handler(Looper.getMainLooper()); /** * Called when posting a new notification that should alert the user and appear on screen. * Adds the notification to be managed. * @param entry entry to show */ public void showNotification(@NonNull NotificationData.Entry entry) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "showNotification"); } addAlertEntry(entry); updateNotification(entry.key, true /* alert */); entry.setInterruption(); } /** * Try to remove the notification. May not succeed if the notification has not been shown long * enough and needs to be kept around. * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time * @return true if notification is removed, false otherwise */ public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeNotification"); } AlertEntry alertEntry = mAlertEntries.get(key); if (alertEntry == null) { return true; } if (releaseImmediately || alertEntry.wasShownLongEnough()) { removeAlertEntry(key); } else { alertEntry.removeAsSoonAsPossible(); return false; } return true; } /** * Called when the notification state has been updated. * @param key the key of the entry that was updated * @param alert whether the notification should alert again and force reevaluation of * removal time */ public void updateNotification(@NonNull String key, boolean alert) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "updateNotification"); } AlertEntry alertEntry = mAlertEntries.get(key); if (alertEntry == null) { // the entry was released before this update (i.e by a listener) This can happen // with the groupmanager return; } alertEntry.mEntry.row.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); if (alert) { alertEntry.updateEntry(true /* updatePostTime */); } } /** * Clears all managed notifications. */ public void releaseAllImmediately() { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "releaseAllImmediately"); } // A copy is necessary here as we are changing the underlying map. This would cause // undefined behavior if we iterated over the key set directly. ArraySet<String> keysToRemove = new ArraySet<>(mAlertEntries.keySet()); for (String key : keysToRemove) { removeAlertEntry(key); } } /** * Returns the entry if it is managed by this manager. * @param key key of notification * @return the entry */ @Nullable public NotificationData.Entry getEntry(@NonNull String key) { AlertEntry entry = mAlertEntries.get(key); return entry != null ? entry.mEntry : null; } /** * Returns the stream of all current notifications managed by this manager. * @return all entries */ @NonNull public Stream<NotificationData.Entry> getAllEntries() { return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry); } /** * Whether or not there are any active alerting notifications. * @return true if there is an alert, false otherwise */ public boolean hasNotifications() { return !mAlertEntries.isEmpty(); } /** * 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) { return mAlertEntries.containsKey(key); } /** * Add a new entry and begin managing it. * @param entry the entry to add */ protected final void addAlertEntry(@NonNull NotificationData.Entry entry) { AlertEntry alertEntry = createAlertEntry(); alertEntry.setEntry(entry); mAlertEntries.put(entry.key, alertEntry); onAlertEntryAdded(alertEntry); entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } /** * Manager-specific logic that should occur when an entry is added. * @param alertEntry alert entry added */ protected abstract void onAlertEntryAdded(@NonNull AlertEntry alertEntry); /** * Remove a notification and reset the alert entry. * @param key key of notification to remove */ protected final void removeAlertEntry(@NonNull String key) { AlertEntry alertEntry = mAlertEntries.get(key); if (alertEntry == null) { return; } NotificationData.Entry entry = alertEntry.mEntry; mAlertEntries.remove(key); onAlertEntryRemoved(alertEntry); entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); alertEntry.reset(); } /** * Manager-specific logic that should occur when an alert entry is removed. * @param alertEntry alert entry removed */ protected abstract void onAlertEntryRemoved(@NonNull AlertEntry alertEntry); /** * Returns a new alert entry instance. * @return a new AlertEntry */ protected AlertEntry createAlertEntry() { return new AlertEntry(); } protected class AlertEntry implements Comparable<AlertEntry> { @Nullable public NotificationData.Entry mEntry; public long mPostTime; public long mEarliestRemovaltime; @Nullable protected Runnable mRemoveAlertRunnable; public void setEntry(@Nullable final NotificationData.Entry entry) { setEntry(entry, () -> removeAlertEntry(entry.key)); } public void setEntry(@Nullable final NotificationData.Entry entry, @Nullable Runnable removeAlertRunnable) { mEntry = entry; mRemoveAlertRunnable = removeAlertRunnable; mPostTime = calculatePostTime(); updateEntry(true /* updatePostTime */); } /** * Updates an entry's removal time. * @param updatePostTime whether or not to refresh the post time */ public void updateEntry(boolean updatePostTime) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "updateEntry"); } long currentTime = mClock.currentTimeMillis(); mEarliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { mPostTime = Math.max(mPostTime, currentTime); } removeAutoRemovalCallbacks(); if (!isSticky()) { long finishTime = mPostTime + mAutoDismissNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); mHandler.postDelayed(mRemoveAlertRunnable, removeDelay); } } /** * Whether or not the notification is "sticky" i.e. should stay on screen regardless * of the timer and should be removed externally. * @return true if the notification is sticky */ protected boolean isSticky() { return false; } /** * Whether the notification has been on screen long enough and can be removed. * @return true if the notification has been on screen long enough */ public boolean wasShownLongEnough() { return mEarliestRemovaltime < mClock.currentTimeMillis(); } @Override public int compareTo(@NonNull AlertEntry alertEntry) { return (mPostTime < alertEntry.mPostTime) ? 1 : ((mPostTime == alertEntry.mPostTime) ? mEntry.key.compareTo(alertEntry.mEntry.key) : -1); } public void reset() { mEntry = null; removeAutoRemovalCallbacks(); mRemoveAlertRunnable = null; } /** * Clear any pending removal runnables. */ public void removeAutoRemovalCallbacks() { if (mRemoveAlertRunnable != null) { mHandler.removeCallbacks(mRemoveAlertRunnable); } } /** * Remove the alert at the earliest allowed removal time. */ public void removeAsSoonAsPossible() { if (mRemoveAlertRunnable != null) { removeAutoRemovalCallbacks(); mHandler.postDelayed(mRemoveAlertRunnable, mEarliestRemovaltime - mClock.currentTimeMillis()); } } /** * Calculate what the post time of a notification is at some current time. * @return the post time */ protected long calculatePostTime() { return mClock.currentTimeMillis(); } } protected final static class Clock { public long currentTimeMillis() { return SystemClock.elapsedRealtime(); } } } packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +3 −3 Original line number Diff line number Diff line Loading @@ -487,7 +487,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. public void removeNotification(String key, NotificationListenerService.RankingMap ranking) { boolean deferRemoval = false; abortExistingInflation(key); if (mHeadsUpManager.isHeadsUp(key)) { if (mHeadsUpManager.contains(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 Loading Loading @@ -1060,7 +1060,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. // We don't want this to be interrupting anymore, lets remove it mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); } else { mHeadsUpManager.updateNotification(entry, alertAgain); mHeadsUpManager.updateNotification(entry.key, alertAgain); } } else if (shouldPeek && alertAgain) { // This notification was updated to be a heads-up, show it! Loading @@ -1069,7 +1069,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } protected boolean isHeadsUp(String key) { return mHeadsUpManager.isHeadsUp(key); return mHeadsUpManager.contains(key); } public boolean isNotificationKeptForRemoteInput(String key) { Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +30 −46 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import android.view.Gravity; import android.view.View; import android.view.ViewTreeObserver; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.ScreenDecorations; Loading @@ -55,7 +54,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener { private static final String TAG = "HeadsUpManagerPhone"; private static final boolean DEBUG = false; private final View mStatusBarWindowView; private final NotificationGroupManager mGroupManager; Loading Loading @@ -114,7 +112,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, addListener(new OnHeadsUpChangedListener() { @Override public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) { if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged"); if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "onHeadsUpPinnedModeChanged"); } updateTouchableRegionListener(); } }); Loading Loading @@ -153,7 +153,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, */ public boolean shouldSwallowClick(@NonNull String key) { HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); return entry != null && mClock.currentTimeMillis() < entry.postTime; return entry != null && mClock.currentTimeMillis() < entry.mPostTime; } public void onExpandingFinished() { Loading @@ -162,9 +162,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mReleaseOnExpandFinish = false; } else { for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { if (isHeadsUp(entry.key)) { if (contains(entry.key)) { // Maybe the heads-up was removed already removeHeadsUpEntry(entry); removeAlertEntry(entry.key); } } } Loading Loading @@ -235,13 +235,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } } @VisibleForTesting public void removeMinimumDisplayTimeForTesting() { mMinimumDisplayTime = 0; mHeadsUpNotificationDecay = 0; mTouchAcceptanceDelay = 0; } /////////////////////////////////////////////////////////////////////////////////////////////// // HeadsUpManager public methods overrides: Loading @@ -250,12 +243,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return mTrackingHeadsUp; } @Override public void snooze() { super.snooze(); mReleaseOnExpandFinish = true; } /** * React to the removal of the notification in the heads up. * Loading @@ -263,14 +250,15 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, * for a bit since it wasn't shown long enough */ @Override public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { return super.removeNotification(key, ignoreEarliestRemovalTime); } else { HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key); entry.removeAsSoonAsPossible(); return false; public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { return super.removeNotification(key, canRemoveImmediately(key) || releaseImmediately); } @Override public void snooze() { super.snooze(); mReleaseOnExpandFinish = true; } public void addSwipedOutNotification(@NonNull String key) { Loading Loading @@ -354,9 +342,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, public void onReorderingAllowed() { mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isHeadsUp(entry.key)) { if (contains(entry.key)) { // Maybe the heads-up was removed already removeHeadsUpEntry(entry); removeAlertEntry(entry.key); } } mEntriesToRemoveWhenReorderingAllowed.clear(); Loading @@ -367,14 +355,14 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, // HeadsUpManager utility (protected) methods overrides: @Override protected HeadsUpEntry createHeadsUpEntry() { protected HeadsUpEntry createAlertEntry() { return mEntryPool.acquire(); } @Override protected void releaseHeadsUpEntry(HeadsUpEntry entry) { entry.reset(); mEntryPool.release((HeadsUpEntryPhone) entry); protected void onAlertEntryRemoved(AlertEntry alertEntry) { super.onAlertEntryRemoved(alertEntry); mEntryPool.release((HeadsUpEntryPhone) alertEntry); } @Override Loading @@ -394,7 +382,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, @Nullable private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) { return (HeadsUpEntryPhone) getHeadsUpEntry(key); return (HeadsUpEntryPhone) mAlertEntries.get(key); } @Nullable Loading @@ -402,7 +390,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return (HeadsUpEntryPhone) getTopHeadsUpEntry(); } private boolean wasShownLongEnough(@NonNull String key) { private boolean canRemoveImmediately(@NonNull String key) { if (mSwipedOutKeys.contains(key)) { // We always instantly dismiss views being manually swiped out. mSwipedOutKeys.remove(key); Loading Loading @@ -461,33 +449,29 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mVisualStabilityManager.addReorderingAllowedCallback( HeadsUpManagerPhone.this); } else if (!mTrackingHeadsUp) { removeHeadsUpEntry(entry); removeAlertEntry(entry.key); } else { mEntriesToRemoveAfterExpand.add(entry); } }; super.setEntry(entry, removeHeadsUpRunnable); } public boolean wasShownLongEnough() { return earliestRemovaltime < mClock.currentTimeMillis(); setEntry(entry, removeHeadsUpRunnable); } @Override public void updateEntry(boolean updatePostTime) { super.updateEntry(updatePostTime); if (mEntriesToRemoveAfterExpand.contains(entry)) { mEntriesToRemoveAfterExpand.remove(entry); if (mEntriesToRemoveAfterExpand.contains(mEntry)) { mEntriesToRemoveAfterExpand.remove(mEntry); } if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { mEntriesToRemoveWhenReorderingAllowed.remove(entry); if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) { mEntriesToRemoveWhenReorderingAllowed.remove(mEntry); } } @Override public void expanded(boolean expanded) { public void setExpanded(boolean expanded) { if (this.expanded == expanded) { return; } Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +9 −7 Original line number Diff line number Diff line Loading @@ -171,7 +171,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { */ private void cleanUpHeadsUpStatesOnAdd(NotificationGroup group, boolean addIsPending) { if (!addIsPending && group.hunSummaryOnNextAddition) { if (!mHeadsUpManager.isHeadsUp(group.summary.key)) { if (!mHeadsUpManager.contains(group.summary.key)) { mHeadsUpManager.showNotification(group.summary); } group.hunSummaryOnNextAddition = false; Loading Loading @@ -208,15 +208,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { NotificationData.Entry entry = children.get(i); if (onlySummaryAlerts(entry) && entry.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(entry.key); mHeadsUpManager.removeNotification( entry.key, true /* releaseImmediately */); } } if (isolatedChild != null && onlySummaryAlerts(isolatedChild) && isolatedChild.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(isolatedChild.key); mHeadsUpManager.removeNotification( isolatedChild.key, true /* releaseImmediately */); } if (releasedChild && !mHeadsUpManager.isHeadsUp(group.summary.key)) { if (releasedChild && !mHeadsUpManager.contains(group.summary.key)) { boolean notifyImmediately = (numChildren - numPendingChildren) > 1; if (notifyImmediately) { mHeadsUpManager.showNotification(group.summary); Loading Loading @@ -546,8 +548,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { // the notification is actually already removed, no need to do heads-up on it. return; } if (mHeadsUpManager.isHeadsUp(child.key)) { mHeadsUpManager.updateNotification(child, true); if (mHeadsUpManager.contains(child.key)) { mHeadsUpManager.updateNotification(child.key, true /* alert */); } else { if (onlySummaryAlerts(entry)) { notificationGroup.lastHeadsUpTransfer = SystemClock.elapsedRealtime(); Loading @@ -556,7 +558,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { } } } mHeadsUpManager.releaseImmediately(entry.key); mHeadsUpManager.removeNotification(entry.key, true /* releaseImmediately */); } private boolean onlySummaryAlerts(NotificationData.Entry entry) { Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +5 −7 Original line number Diff line number Diff line Loading @@ -56,7 +56,6 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.TaskStackBuilder; import android.app.UiModeManager; import android.app.WallpaperColors; import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; Loading @@ -67,8 +66,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; Loading Loading @@ -1414,7 +1411,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPerformRemoveNotification(StatusBarNotification n) { if (mStackScroller.hasPulsingNotifications() && !mHeadsUpManager.hasHeadsUpNotifications()) { !mHeadsUpManager.hasNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); Loading Loading @@ -4835,7 +4832,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); if (mHeadsUpManager.hasHeadsUpNotifications()) { if (mHeadsUpManager.hasNotifications()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. setPulsing(true); Loading Loading @@ -5108,7 +5105,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean wasOccluded = mIsOccluded; dismissKeyguardThenExecute(() -> { // TODO: Some of this code may be able to move to NotificationEntryManager. if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { if (mHeadsUpManager != null && mHeadsUpManager.contains(notificationKey)) { // Release the HUN notification to the shade. if (isPresenterFullyCollapsed()) { Loading @@ -5117,7 +5114,8 @@ public class StatusBar extends SystemUI implements DemoMode, // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will // become canceled shortly by NoMan, but we can't assume that. mHeadsUpManager.releaseImmediately(notificationKey); mHeadsUpManager.removeNotification(sbn.getKey(), true /* releaseImmediately */); } StatusBarNotification parentToCancel = null; if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java 0 → 100644 +317 −0 Original line number Diff line number Diff line /* * 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.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.statusbar.notification.NotificationData; import java.util.stream.Stream; /** * A manager which contains notification alerting functionality, providing methods to add and * remove notifications that appear on screen for a period of time and dismiss themselves at the * appropriate time. These include heads up notifications and ambient pulses. */ public abstract class AlertingNotificationManager { private static final String TAG = "AlertNotifManager"; protected final Clock mClock = new Clock(); protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>(); protected int mMinimumDisplayTime; protected int mAutoDismissNotificationDecay; @VisibleForTesting public Handler mHandler = new Handler(Looper.getMainLooper()); /** * Called when posting a new notification that should alert the user and appear on screen. * Adds the notification to be managed. * @param entry entry to show */ public void showNotification(@NonNull NotificationData.Entry entry) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "showNotification"); } addAlertEntry(entry); updateNotification(entry.key, true /* alert */); entry.setInterruption(); } /** * Try to remove the notification. May not succeed if the notification has not been shown long * enough and needs to be kept around. * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time * @return true if notification is removed, false otherwise */ public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeNotification"); } AlertEntry alertEntry = mAlertEntries.get(key); if (alertEntry == null) { return true; } if (releaseImmediately || alertEntry.wasShownLongEnough()) { removeAlertEntry(key); } else { alertEntry.removeAsSoonAsPossible(); return false; } return true; } /** * Called when the notification state has been updated. * @param key the key of the entry that was updated * @param alert whether the notification should alert again and force reevaluation of * removal time */ public void updateNotification(@NonNull String key, boolean alert) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "updateNotification"); } AlertEntry alertEntry = mAlertEntries.get(key); if (alertEntry == null) { // the entry was released before this update (i.e by a listener) This can happen // with the groupmanager return; } alertEntry.mEntry.row.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); if (alert) { alertEntry.updateEntry(true /* updatePostTime */); } } /** * Clears all managed notifications. */ public void releaseAllImmediately() { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "releaseAllImmediately"); } // A copy is necessary here as we are changing the underlying map. This would cause // undefined behavior if we iterated over the key set directly. ArraySet<String> keysToRemove = new ArraySet<>(mAlertEntries.keySet()); for (String key : keysToRemove) { removeAlertEntry(key); } } /** * Returns the entry if it is managed by this manager. * @param key key of notification * @return the entry */ @Nullable public NotificationData.Entry getEntry(@NonNull String key) { AlertEntry entry = mAlertEntries.get(key); return entry != null ? entry.mEntry : null; } /** * Returns the stream of all current notifications managed by this manager. * @return all entries */ @NonNull public Stream<NotificationData.Entry> getAllEntries() { return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry); } /** * Whether or not there are any active alerting notifications. * @return true if there is an alert, false otherwise */ public boolean hasNotifications() { return !mAlertEntries.isEmpty(); } /** * 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) { return mAlertEntries.containsKey(key); } /** * Add a new entry and begin managing it. * @param entry the entry to add */ protected final void addAlertEntry(@NonNull NotificationData.Entry entry) { AlertEntry alertEntry = createAlertEntry(); alertEntry.setEntry(entry); mAlertEntries.put(entry.key, alertEntry); onAlertEntryAdded(alertEntry); entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } /** * Manager-specific logic that should occur when an entry is added. * @param alertEntry alert entry added */ protected abstract void onAlertEntryAdded(@NonNull AlertEntry alertEntry); /** * Remove a notification and reset the alert entry. * @param key key of notification to remove */ protected final void removeAlertEntry(@NonNull String key) { AlertEntry alertEntry = mAlertEntries.get(key); if (alertEntry == null) { return; } NotificationData.Entry entry = alertEntry.mEntry; mAlertEntries.remove(key); onAlertEntryRemoved(alertEntry); entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); alertEntry.reset(); } /** * Manager-specific logic that should occur when an alert entry is removed. * @param alertEntry alert entry removed */ protected abstract void onAlertEntryRemoved(@NonNull AlertEntry alertEntry); /** * Returns a new alert entry instance. * @return a new AlertEntry */ protected AlertEntry createAlertEntry() { return new AlertEntry(); } protected class AlertEntry implements Comparable<AlertEntry> { @Nullable public NotificationData.Entry mEntry; public long mPostTime; public long mEarliestRemovaltime; @Nullable protected Runnable mRemoveAlertRunnable; public void setEntry(@Nullable final NotificationData.Entry entry) { setEntry(entry, () -> removeAlertEntry(entry.key)); } public void setEntry(@Nullable final NotificationData.Entry entry, @Nullable Runnable removeAlertRunnable) { mEntry = entry; mRemoveAlertRunnable = removeAlertRunnable; mPostTime = calculatePostTime(); updateEntry(true /* updatePostTime */); } /** * Updates an entry's removal time. * @param updatePostTime whether or not to refresh the post time */ public void updateEntry(boolean updatePostTime) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "updateEntry"); } long currentTime = mClock.currentTimeMillis(); mEarliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { mPostTime = Math.max(mPostTime, currentTime); } removeAutoRemovalCallbacks(); if (!isSticky()) { long finishTime = mPostTime + mAutoDismissNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); mHandler.postDelayed(mRemoveAlertRunnable, removeDelay); } } /** * Whether or not the notification is "sticky" i.e. should stay on screen regardless * of the timer and should be removed externally. * @return true if the notification is sticky */ protected boolean isSticky() { return false; } /** * Whether the notification has been on screen long enough and can be removed. * @return true if the notification has been on screen long enough */ public boolean wasShownLongEnough() { return mEarliestRemovaltime < mClock.currentTimeMillis(); } @Override public int compareTo(@NonNull AlertEntry alertEntry) { return (mPostTime < alertEntry.mPostTime) ? 1 : ((mPostTime == alertEntry.mPostTime) ? mEntry.key.compareTo(alertEntry.mEntry.key) : -1); } public void reset() { mEntry = null; removeAutoRemovalCallbacks(); mRemoveAlertRunnable = null; } /** * Clear any pending removal runnables. */ public void removeAutoRemovalCallbacks() { if (mRemoveAlertRunnable != null) { mHandler.removeCallbacks(mRemoveAlertRunnable); } } /** * Remove the alert at the earliest allowed removal time. */ public void removeAsSoonAsPossible() { if (mRemoveAlertRunnable != null) { removeAutoRemovalCallbacks(); mHandler.postDelayed(mRemoveAlertRunnable, mEarliestRemovaltime - mClock.currentTimeMillis()); } } /** * Calculate what the post time of a notification is at some current time. * @return the post time */ protected long calculatePostTime() { return mClock.currentTimeMillis(); } } protected final static class Clock { public long currentTimeMillis() { return SystemClock.elapsedRealtime(); } } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +3 −3 Original line number Diff line number Diff line Loading @@ -487,7 +487,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. public void removeNotification(String key, NotificationListenerService.RankingMap ranking) { boolean deferRemoval = false; abortExistingInflation(key); if (mHeadsUpManager.isHeadsUp(key)) { if (mHeadsUpManager.contains(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 Loading Loading @@ -1060,7 +1060,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. // We don't want this to be interrupting anymore, lets remove it mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); } else { mHeadsUpManager.updateNotification(entry, alertAgain); mHeadsUpManager.updateNotification(entry.key, alertAgain); } } else if (shouldPeek && alertAgain) { // This notification was updated to be a heads-up, show it! Loading @@ -1069,7 +1069,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } protected boolean isHeadsUp(String key) { return mHeadsUpManager.isHeadsUp(key); return mHeadsUpManager.contains(key); } public boolean isNotificationKeptForRemoteInput(String key) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +30 −46 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import android.view.Gravity; import android.view.View; import android.view.ViewTreeObserver; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.ScreenDecorations; Loading @@ -55,7 +54,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener { private static final String TAG = "HeadsUpManagerPhone"; private static final boolean DEBUG = false; private final View mStatusBarWindowView; private final NotificationGroupManager mGroupManager; Loading Loading @@ -114,7 +112,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, addListener(new OnHeadsUpChangedListener() { @Override public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) { if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged"); if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "onHeadsUpPinnedModeChanged"); } updateTouchableRegionListener(); } }); Loading Loading @@ -153,7 +153,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, */ public boolean shouldSwallowClick(@NonNull String key) { HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); return entry != null && mClock.currentTimeMillis() < entry.postTime; return entry != null && mClock.currentTimeMillis() < entry.mPostTime; } public void onExpandingFinished() { Loading @@ -162,9 +162,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mReleaseOnExpandFinish = false; } else { for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { if (isHeadsUp(entry.key)) { if (contains(entry.key)) { // Maybe the heads-up was removed already removeHeadsUpEntry(entry); removeAlertEntry(entry.key); } } } Loading Loading @@ -235,13 +235,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } } @VisibleForTesting public void removeMinimumDisplayTimeForTesting() { mMinimumDisplayTime = 0; mHeadsUpNotificationDecay = 0; mTouchAcceptanceDelay = 0; } /////////////////////////////////////////////////////////////////////////////////////////////// // HeadsUpManager public methods overrides: Loading @@ -250,12 +243,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return mTrackingHeadsUp; } @Override public void snooze() { super.snooze(); mReleaseOnExpandFinish = true; } /** * React to the removal of the notification in the heads up. * Loading @@ -263,14 +250,15 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, * for a bit since it wasn't shown long enough */ @Override public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { return super.removeNotification(key, ignoreEarliestRemovalTime); } else { HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key); entry.removeAsSoonAsPossible(); return false; public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { return super.removeNotification(key, canRemoveImmediately(key) || releaseImmediately); } @Override public void snooze() { super.snooze(); mReleaseOnExpandFinish = true; } public void addSwipedOutNotification(@NonNull String key) { Loading Loading @@ -354,9 +342,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, public void onReorderingAllowed() { mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isHeadsUp(entry.key)) { if (contains(entry.key)) { // Maybe the heads-up was removed already removeHeadsUpEntry(entry); removeAlertEntry(entry.key); } } mEntriesToRemoveWhenReorderingAllowed.clear(); Loading @@ -367,14 +355,14 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, // HeadsUpManager utility (protected) methods overrides: @Override protected HeadsUpEntry createHeadsUpEntry() { protected HeadsUpEntry createAlertEntry() { return mEntryPool.acquire(); } @Override protected void releaseHeadsUpEntry(HeadsUpEntry entry) { entry.reset(); mEntryPool.release((HeadsUpEntryPhone) entry); protected void onAlertEntryRemoved(AlertEntry alertEntry) { super.onAlertEntryRemoved(alertEntry); mEntryPool.release((HeadsUpEntryPhone) alertEntry); } @Override Loading @@ -394,7 +382,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, @Nullable private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) { return (HeadsUpEntryPhone) getHeadsUpEntry(key); return (HeadsUpEntryPhone) mAlertEntries.get(key); } @Nullable Loading @@ -402,7 +390,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return (HeadsUpEntryPhone) getTopHeadsUpEntry(); } private boolean wasShownLongEnough(@NonNull String key) { private boolean canRemoveImmediately(@NonNull String key) { if (mSwipedOutKeys.contains(key)) { // We always instantly dismiss views being manually swiped out. mSwipedOutKeys.remove(key); Loading Loading @@ -461,33 +449,29 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mVisualStabilityManager.addReorderingAllowedCallback( HeadsUpManagerPhone.this); } else if (!mTrackingHeadsUp) { removeHeadsUpEntry(entry); removeAlertEntry(entry.key); } else { mEntriesToRemoveAfterExpand.add(entry); } }; super.setEntry(entry, removeHeadsUpRunnable); } public boolean wasShownLongEnough() { return earliestRemovaltime < mClock.currentTimeMillis(); setEntry(entry, removeHeadsUpRunnable); } @Override public void updateEntry(boolean updatePostTime) { super.updateEntry(updatePostTime); if (mEntriesToRemoveAfterExpand.contains(entry)) { mEntriesToRemoveAfterExpand.remove(entry); if (mEntriesToRemoveAfterExpand.contains(mEntry)) { mEntriesToRemoveAfterExpand.remove(mEntry); } if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { mEntriesToRemoveWhenReorderingAllowed.remove(entry); if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) { mEntriesToRemoveWhenReorderingAllowed.remove(mEntry); } } @Override public void expanded(boolean expanded) { public void setExpanded(boolean expanded) { if (this.expanded == expanded) { return; } Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +9 −7 Original line number Diff line number Diff line Loading @@ -171,7 +171,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { */ private void cleanUpHeadsUpStatesOnAdd(NotificationGroup group, boolean addIsPending) { if (!addIsPending && group.hunSummaryOnNextAddition) { if (!mHeadsUpManager.isHeadsUp(group.summary.key)) { if (!mHeadsUpManager.contains(group.summary.key)) { mHeadsUpManager.showNotification(group.summary); } group.hunSummaryOnNextAddition = false; Loading Loading @@ -208,15 +208,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { NotificationData.Entry entry = children.get(i); if (onlySummaryAlerts(entry) && entry.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(entry.key); mHeadsUpManager.removeNotification( entry.key, true /* releaseImmediately */); } } if (isolatedChild != null && onlySummaryAlerts(isolatedChild) && isolatedChild.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(isolatedChild.key); mHeadsUpManager.removeNotification( isolatedChild.key, true /* releaseImmediately */); } if (releasedChild && !mHeadsUpManager.isHeadsUp(group.summary.key)) { if (releasedChild && !mHeadsUpManager.contains(group.summary.key)) { boolean notifyImmediately = (numChildren - numPendingChildren) > 1; if (notifyImmediately) { mHeadsUpManager.showNotification(group.summary); Loading Loading @@ -546,8 +548,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { // the notification is actually already removed, no need to do heads-up on it. return; } if (mHeadsUpManager.isHeadsUp(child.key)) { mHeadsUpManager.updateNotification(child, true); if (mHeadsUpManager.contains(child.key)) { mHeadsUpManager.updateNotification(child.key, true /* alert */); } else { if (onlySummaryAlerts(entry)) { notificationGroup.lastHeadsUpTransfer = SystemClock.elapsedRealtime(); Loading @@ -556,7 +558,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { } } } mHeadsUpManager.releaseImmediately(entry.key); mHeadsUpManager.removeNotification(entry.key, true /* releaseImmediately */); } private boolean onlySummaryAlerts(NotificationData.Entry entry) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +5 −7 Original line number Diff line number Diff line Loading @@ -56,7 +56,6 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.TaskStackBuilder; import android.app.UiModeManager; import android.app.WallpaperColors; import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; Loading @@ -67,8 +66,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; Loading Loading @@ -1414,7 +1411,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPerformRemoveNotification(StatusBarNotification n) { if (mStackScroller.hasPulsingNotifications() && !mHeadsUpManager.hasHeadsUpNotifications()) { !mHeadsUpManager.hasNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); Loading Loading @@ -4835,7 +4832,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); if (mHeadsUpManager.hasHeadsUpNotifications()) { if (mHeadsUpManager.hasNotifications()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. setPulsing(true); Loading Loading @@ -5108,7 +5105,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean wasOccluded = mIsOccluded; dismissKeyguardThenExecute(() -> { // TODO: Some of this code may be able to move to NotificationEntryManager. if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { if (mHeadsUpManager != null && mHeadsUpManager.contains(notificationKey)) { // Release the HUN notification to the shade. if (isPresenterFullyCollapsed()) { Loading @@ -5117,7 +5114,8 @@ public class StatusBar extends SystemUI implements DemoMode, // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will // become canceled shortly by NoMan, but we can't assume that. mHeadsUpManager.releaseImmediately(notificationKey); mHeadsUpManager.removeNotification(sbn.getKey(), true /* releaseImmediately */); } StatusBarNotification parentToCancel = null; if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { Loading