diff options
14 files changed, 882 insertions, 449 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 3ebeb4d45c26..3dfb9130af2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -379,7 +379,7 @@ public class CarStatusBar extends StatusBar implements // 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 removing all heads- // up notifications. - mHeadsUpManager.removeAllHeadsUpEntries(); + mHeadsUpManager.releaseAllImmediately(); super.animateExpandNotificationsPanel(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java new file mode 100644 index 000000000000..c45c5386eda4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.support.v4.util.ArraySet; +import android.util.Log; +import android.util.Pools; +import android.view.View; +import android.view.ViewTreeObserver; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Stack; + +/** + * A implementation of HeadsUpManager for phone and car. + */ +public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, + ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, + OnHeadsUpChangedListener { + private static final String TAG = "HeadsUpManagerPhone"; + private static final boolean DEBUG = false; + + private final View mStatusBarWindowView; + private final int mStatusBarHeight; + private final NotificationGroupManager mGroupManager; + private final StatusBar mBar; + private final VisualStabilityManager mVisualStabilityManager; + + private boolean mReleaseOnExpandFinish; + private boolean mTrackingHeadsUp; + private HashSet<String> mSwipedOutKeys = new HashSet<>(); + private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); + private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed + = new ArraySet<>(); + private boolean mIsExpanded; + private int[] mTmpTwoArray = new int[2]; + private boolean mHeadsUpGoingAway; + private boolean mWaitingOnCollapseWhenGoingAway; + private boolean mIsObserving; + private int mStatusBarState; + + private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() { + private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>(); + + @Override + public HeadsUpEntryPhone acquire() { + if (!mPoolObjects.isEmpty()) { + return mPoolObjects.pop(); + } + return new HeadsUpEntryPhone(); + } + + @Override + public boolean release(@NonNull HeadsUpEntryPhone instance) { + instance.reset(); + mPoolObjects.push(instance); + return true; + } + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Constructor: + + public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView, + @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar, + @NonNull VisualStabilityManager visualStabilityManager) { + super(context); + + mStatusBarWindowView = statusBarWindowView; + mGroupManager = groupManager; + mBar = bar; + mVisualStabilityManager = visualStabilityManager; + + Resources resources = mContext.getResources(); + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + + addListener(new OnHeadsUpChangedListener() { + @Override + public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) { + if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged"); + updateTouchableRegionListener(); + } + }); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Public methods: + + /** + * Decides whether a click is invalid for a notification, i.e it has not been shown long enough + * that a user might have consciously clicked on it. + * + * @param key the key of the touched notification + * @return whether the touch is invalid and should be discarded + */ + public boolean shouldSwallowClick(@NonNull String key) { + HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); + if (entry != null && mClock.currentTimeMillis() < entry.postTime) { + return true; + } + return false; + } + + public void onExpandingFinished() { + if (mReleaseOnExpandFinish) { + releaseAllImmediately(); + mReleaseOnExpandFinish = false; + } else { + for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + } + mEntriesToRemoveAfterExpand.clear(); + } + + /** + * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry + * from the list even after a Heads Up Notification is gone. + */ + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + /** + * Notify that the status bar panel gets expanded or collapsed. + * + * @param isExpanded True to notify expanded, false to notify collapsed. + */ + public void setIsPanelExpanded(boolean isExpanded) { + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + if (isExpanded) { + // make sure our state is sane + mWaitingOnCollapseWhenGoingAway = false; + mHeadsUpGoingAway = false; + updateTouchableRegionListener(); + } + } + } + + /** + * Set the current state of the statusbar. + */ + public void setStatusBarState(int statusBarState) { + mStatusBarState = statusBarState; + } + + /** + * Set that we are exiting the headsUp pinned mode, but some notifications might still be + * animating out. This is used to keep the touchable regions in a sane state. + */ + public void setHeadsUpGoingAway(boolean headsUpGoingAway) { + if (headsUpGoingAway != mHeadsUpGoingAway) { + mHeadsUpGoingAway = headsUpGoingAway; + if (!headsUpGoingAway) { + waitForStatusBarLayout(); + } + updateTouchableRegionListener(); + } + } + + /** + * Notifies that a remote input textbox in notification gets active or inactive. + * @param entry The entry of the target notification. + * @param remoteInputActive True to notify active, False to notify inactive. + */ + public void setRemoteInputActive( + @NonNull NotificationData.Entry entry, boolean remoteInputActive) { + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key); + if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { + headsUpEntry.remoteInputActive = remoteInputActive; + if (remoteInputActive) { + headsUpEntry.removeAutoRemovalCallbacks(); + } else { + headsUpEntry.updateEntry(false /* updatePostTime */); + } + } + } + + @VisibleForTesting + public void removeMinimumDisplayTimeForTesting() { + mMinimumDisplayTime = 1; + mHeadsUpNotificationDecay = 1; + mTouchAcceptanceDelay = 1; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager public methods overrides: + + @Override + public boolean isTrackingHeadsUp() { + return mTrackingHeadsUp; + } + + @Override + public void snooze() { + super.snooze(); + mReleaseOnExpandFinish = true; + } + + /** + * React to the removal of the notification in the heads up. + * + * @return true if the notification was removed and false if it still needs to be kept around + * 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 void addSwipedOutNotification(@NonNull String key) { + mSwipedOutKeys.add(key); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Dumpable overrides: + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("HeadsUpManagerPhone state:"); + dumpInternal(fd, pw, args); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // ViewTreeObserver.OnComputeInternalInsetsListener overrides: + + /** + * Overridden from TreeObserver. + */ + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + if (mIsExpanded || mBar.isBouncerShowing()) { + // The touchable region is always the full area when expanded + return; + } + if (hasPinnedHeadsUp()) { + ExpandableNotificationRow topEntry = getTopEntry().row; + if (topEntry.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); + if (groupSummary != null) { + topEntry = groupSummary; + } + } + topEntry.getLocationOnScreen(mTmpTwoArray); + int minX = mTmpTwoArray[0]; + int maxX = mTmpTwoArray[0] + topEntry.getWidth(); + int maxY = topEntry.getIntrinsicHeight(); + + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(minX, 0, maxX, maxY); + } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // VisualStabilityManager.Callback overrides: + + @Override + public void onReorderingAllowed() { + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); + for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + mEntriesToRemoveWhenReorderingAllowed.clear(); + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager utility (protected) methods overrides: + + @Override + protected HeadsUpEntry createHeadsUpEntry() { + return mEntryPool.acquire(); + } + + @Override + protected void releaseHeadsUpEntry(HeadsUpEntry entry) { + mEntryPool.release((HeadsUpEntryPhone) entry); + } + + @Override + protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { + return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded + || super.shouldHeadsUpBecomePinned(entry); + } + + @Override + protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dumpInternal(fd, pw, args); + pw.print(" mStatusBarState="); pw.println(mStatusBarState); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Protected utility methods: + + @Nullable + protected HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) { + return (HeadsUpEntryPhone) getHeadsUpEntry(key); + } + + @Nullable + protected HeadsUpEntryPhone getTopHeadsUpEntryPhone() { + return (HeadsUpEntryPhone) getTopHeadsUpEntry(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Private utility methods: + + private boolean wasShownLongEnough(@NonNull String key) { + if (mSwipedOutKeys.contains(key)) { + // We always instantly dismiss views being manually swiped out. + mSwipedOutKeys.remove(key); + return true; + } + + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key); + HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone(); + if (headsUpEntry != topEntry) { + return true; + } + return headsUpEntry.wasShownLongEnough(); + } + + /** + * We need to wait on the whole panel to collapse, before we can remove the touchable region + * listener. + */ + private void waitForStatusBarLayout() { + mWaitingOnCollapseWhenGoingAway = true; + mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { + mStatusBarWindowView.removeOnLayoutChangeListener(this); + mWaitingOnCollapseWhenGoingAway = false; + updateTouchableRegionListener(); + } + } + }); + } + + private void updateTouchableRegionListener() { + boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway + || mWaitingOnCollapseWhenGoingAway; + if (shouldObserve == mIsObserving) { + return; + } + if (shouldObserve) { + mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + mStatusBarWindowView.requestLayout(); + } else { + mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + mIsObserving = shouldObserve; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpEntryPhone: + + protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry { + public void setEntry(@NonNull final NotificationData.Entry entry) { + Runnable removeHeadsUpRunnable = () -> { + if (!mVisualStabilityManager.isReorderingAllowed()) { + mEntriesToRemoveWhenReorderingAllowed.add(entry); + mVisualStabilityManager.addReorderingAllowedCallback( + HeadsUpManagerPhone.this); + } else if (!mTrackingHeadsUp) { + removeHeadsUpEntry(entry); + } else { + mEntriesToRemoveAfterExpand.add(entry); + } + }; + + super.setEntry(entry, removeHeadsUpRunnable); + } + + public boolean wasShownLongEnough() { + return earliestRemovaltime < mClock.currentTimeMillis(); + } + + @Override + public void updateEntry(boolean updatePostTime) { + super.updateEntry(updatePostTime); + + if (mEntriesToRemoveAfterExpand.contains(entry)) { + mEntriesToRemoveAfterExpand.remove(entry); + } + if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { + mEntriesToRemoveWhenReorderingAllowed.remove(entry); + } + } + + @Override + public void expanded(boolean expanded) { + if (this.expanded == expanded) { + return; + } + + this.expanded = expanded; + if (expanded) { + removeAutoRemovalCallbacks(); + } else { + updateEntry(false /* updatePostTime */); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index c85571c1895d..2bfdefe39017 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -23,7 +23,7 @@ import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; /** @@ -31,7 +31,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; */ public class HeadsUpTouchHelper implements Gefingerpoken { - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private NotificationStackScrollLayout mStackScroller; private int mTrackingPointer; private float mTouchSlop; @@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private NotificationPanelView mPanel; private ExpandableNotificationRow mPickedChild; - public HeadsUpTouchHelper(HeadsUpManager headsUpManager, + public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, NotificationStackScrollLayout stackScroller, NotificationPanelView notificationPanelView) { mHeadsUpManager = headsUpManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 31b8159d3cdf..2111d2ef5d87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -68,7 +68,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -1571,7 +1571,7 @@ public class NotificationPanelView extends PanelView implements private void updatePanelExpanded() { boolean isExpanded = !isFullyCollapsed(); if (mPanelExpanded != isExpanded) { - mHeadsUpManager.setIsExpanded(isExpanded); + mHeadsUpManager.setIsPanelExpanded(isExpanded); mStatusBar.setPanelExpanded(isExpanded); mPanelExpanded = isExpanded; } @@ -2338,7 +2338,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { super.setHeadsUpManager(headsUpManager); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, this); @@ -2630,8 +2630,8 @@ public class NotificationPanelView extends PanelView implements } } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { - mKeyguardStatusView.setPulsing(pulsing != null); + public void setPulsing(boolean pulsing) { + mKeyguardStatusView.setPulsing(pulsing); positionClockAndNotifications(); mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1] + mKeyguardStatusView.getClockBottom()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 2b7e4747a837..6daabede7f32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -50,7 +50,7 @@ import com.android.systemui.classifier.FalsingManager; import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -75,7 +75,7 @@ public abstract class PanelView extends FrameLayout { } protected StatusBar mStatusBar; - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private float mPeekHeight; private float mHintDistance; @@ -1252,7 +1252,7 @@ public abstract class PanelView extends FrameLayout { */ protected abstract int getClearAllHeight(); - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; } 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 426268ba490c..116e3f93bf8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -208,6 +208,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -219,6 +220,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; @@ -802,15 +804,14 @@ public class StatusBar extends SystemUI implements DemoMode, .commit(); mIconController = Dependency.get(StatusBarIconController.class); - mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager); - mHeadsUpManager.setBar(this); + mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this, + mVisualStabilityManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanel); mHeadsUpManager.addListener(mGroupManager); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanel.setHeadsUpManager(mHeadsUpManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); - mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); putComponent(HeadsUpManager.class, mHeadsUpManager); mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager); @@ -1341,7 +1342,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPerformRemoveNotification(StatusBarNotification n) { - if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { + if (mStackScroller.hasPulsingNotifications() && + !mHeadsUpManager.hasHeadsUpNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); @@ -2090,9 +2092,8 @@ public class StatusBar extends SystemUI implements DemoMode, } public void maybeEscalateHeadsUp() { - Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries(); - for (HeadsUpManager.HeadsUpEntry entry : entries) { - final StatusBarNotification sbn = entry.entry.notification; + mHeadsUpManager.getAllEntries().forEach(entry -> { + final StatusBarNotification sbn = entry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) { @@ -2102,11 +2103,11 @@ public class StatusBar extends SystemUI implements DemoMode, EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); notification.fullScreenIntent.send(); - entry.entry.notifyFullScreenIntentLaunched(); + entry.notifyFullScreenIntentLaunched(); } catch (PendingIntent.CanceledException e) { } } - } + }); mHeadsUpManager.releaseAllImmediately(); } @@ -4636,24 +4637,22 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries = - mHeadsUpManager.getAllEntries(); - if (!pulsingEntries.isEmpty()) { + if (mHeadsUpManager.hasHeadsUpNotifications()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. - setPulsing(pulsingEntries); + setPulsing(true); } } @Override public void onPulseFinished() { callback.onPulseFinished(); - setPulsing(null); + setPulsing(false); } - private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { + private void setPulsing(boolean pulsing) { mNotificationPanel.setPulsing(pulsing); - mVisualStabilityManager.setPulsing(pulsing != null); + mVisualStabilityManager.setPulsing(pulsing); mIgnoreTouchWhilePulsing = false; } }, reason); @@ -4801,7 +4800,7 @@ public class StatusBar extends SystemUI implements DemoMode, // for heads up notifications - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private AboveShelfObserver mAboveShelfObserver; @@ -4904,7 +4903,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Release the HUN notification to the shade. if (isPresenterFullyCollapsed()) { - HeadsUpManager.setIsClickedNotification(row, true); + HeadsUpUtil.setIsClickedHeadsUpNotification(row, true); } // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 53dfb244c776..a2b896dc015b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,118 +16,68 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; +import android.os.SystemClock; import android.os.Handler; import android.os.Looper; -import android.os.SystemClock; -import android.provider.Settings; -import android.support.v4.util.ArraySet; import android.util.ArrayMap; +import android.provider.Settings; import android.util.Log; -import android.util.Pools; -import android.view.View; -import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Iterator; +import java.util.stream.Stream; import java.util.HashMap; import java.util.HashSet; -import java.util.Stack; /** * A manager which handles heads up notifications which is a special mode where * they simply peek from the top of the screen. */ -public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener, - VisualStabilityManager.Callback { +public class HeadsUpManager { private static final String TAG = "HeadsUpManager"; private static final boolean DEBUG = false; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; - - private final int mHeadsUpNotificationDecay; - private final int mMinimumDisplayTime; - private final int mTouchAcceptanceDelay; - private final ArrayMap<String, Long> mSnoozedPackages; - private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); - private final int mDefaultSnoozeLengthMs; - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() { + protected final Clock mClock = new Clock(); + protected final Context mContext; + protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); + protected final Handler mHandler = new Handler(Looper.getMainLooper()); - private Stack<HeadsUpEntry> mPoolObjects = new Stack<>(); + protected int mHeadsUpNotificationDecay; + protected int mMinimumDisplayTime; + protected int mTouchAcceptanceDelay; + protected int mSnoozeLengthMs; + protected boolean mHasPinnedNotification; + protected int mUser; - @Override - public HeadsUpEntry acquire() { - if (!mPoolObjects.isEmpty()) { - return mPoolObjects.pop(); - } - return new HeadsUpEntry(); - } + private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private final ArrayMap<String, Long> mSnoozedPackages; + private final ContentObserver mSettingsObserver; - @Override - public boolean release(HeadsUpEntry instance) { - instance.reset(); - mPoolObjects.push(instance); - return true; - } - }; - - private final View mStatusBarWindowView; - private final int mStatusBarHeight; - private final Context mContext; - private final NotificationGroupManager mGroupManager; - private StatusBar mBar; - private int mSnoozeLengthMs; - private ContentObserver mSettingsObserver; - private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); - private HashSet<String> mSwipedOutKeys = new HashSet<>(); - private int mUser; - private Clock mClock; - private boolean mReleaseOnExpandFinish; - private boolean mTrackingHeadsUp; - private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); - private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed - = new ArraySet<>(); - private boolean mIsExpanded; - private boolean mHasPinnedNotification; - private int[] mTmpTwoArray = new int[2]; - private boolean mHeadsUpGoingAway; - private boolean mWaitingOnCollapseWhenGoingAway; - private boolean mIsObserving; - private boolean mRemoteInputActive; - private float mExpandedHeight; - private VisualStabilityManager mVisualStabilityManager; - private int mStatusBarState; - - public HeadsUpManager(final Context context, View statusBarWindowView, - NotificationGroupManager groupManager) { + public HeadsUpManager(@NonNull final Context context) { mContext = context; - Resources resources = mContext.getResources(); - mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); - mSnoozedPackages = new ArrayMap<>(); - mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); - mSnoozeLengthMs = mDefaultSnoozeLengthMs; + Resources resources = context.getResources(); mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); - mClock = new Clock(); + mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); + mSnoozedPackages = new ArrayMap<>(); + int defaultSnoozeLengthMs = + resources.getInteger(R.integer.heads_up_default_snooze_length_ms); mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs); mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -142,47 +92,26 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL context.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, mSettingsObserver); - mStatusBarWindowView = statusBarWindowView; - mGroupManager = groupManager; - mStatusBarHeight = resources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); - } - - private void updateTouchableRegionListener() { - boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway - || mWaitingOnCollapseWhenGoingAway; - if (shouldObserve == mIsObserving) { - return; - } - if (shouldObserve) { - mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); - mStatusBarWindowView.requestLayout(); - } else { - mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - } - mIsObserving = shouldObserve; } - public void setBar(StatusBar bar) { - mBar = bar; - } - - public void addListener(OnHeadsUpChangedListener listener) { + /** + * Adds an OnHeadUpChangedListener to observe events. + */ + public void addListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.add(listener); } - public void removeListener(OnHeadsUpChangedListener listener) { + /** + * Removes the OnHeadUpChangedListener from the observer list. + */ + public void removeListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.remove(listener); } - public StatusBar getBar() { - return mBar; - } - /** * Called when posting a new notification to the heads up. */ - public void showNotification(NotificationData.Entry headsUp) { + public void showNotification(@NonNull NotificationData.Entry headsUp) { if (DEBUG) Log.v(TAG, "showNotification"); addHeadsUpEntry(headsUp); updateNotification(headsUp, true); @@ -192,7 +121,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL /** * Called when updating or posting a notification to the heads up. */ - public void updateNotification(NotificationData.Entry headsUp, boolean alert) { + public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) { if (DEBUG) Log.v(TAG, "updateNotification"); headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); @@ -204,14 +133,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL // with the groupmanager return; } - headsUpEntry.updateEntry(); + headsUpEntry.updateEntry(true /* updatePostTime */); setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp)); } } - private void addHeadsUpEntry(NotificationData.Entry entry) { - HeadsUpEntry headsUpEntry = mEntryPool.acquire(); - + private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) { + HeadsUpEntry headsUpEntry = createHeadsUpEntry(); // This will also add the entry to the sortedList headsUpEntry.setEntry(entry); mHeadsUpEntries.put(entry.key, headsUpEntry); @@ -223,16 +151,17 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } - private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { - return mStatusBarState != StatusBarState.KEYGUARD - && !mIsExpanded || hasFullScreenIntent(entry); + protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) { + return hasFullScreenIntent(entry); } - private boolean hasFullScreenIntent(NotificationData.Entry entry) { + protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) { return entry.notification.getNotification().fullScreenIntent != null; } - private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) { + protected void setEntryPinned( + @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) { + if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned); ExpandableNotificationRow row = headsUpEntry.entry.row; if (row.isPinned() != isPinned) { row.setPinned(isPinned); @@ -247,33 +176,35 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - private void removeHeadsUpEntry(NotificationData.Entry entry) { + protected void removeHeadsUpEntry(NotificationData.Entry entry) { HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); + onHeadsUpEntryRemoved(remove); + releaseHeadsUpEntry(remove); + } + + protected void onHeadsUpEntryRemoved(HeadsUpEntry remove) { + NotificationData.Entry entry = remove.entry; entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); entry.row.setHeadsUp(false); setEntryPinned(remove, false /* isPinned */); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } - mEntryPool.release(remove); - } - - public void removeAllHeadsUpEntries() { - for (String key : mHeadsUpEntries.keySet()) { - removeHeadsUpEntry(mHeadsUpEntries.get(key).entry); - } } - private void updatePinnedMode() { + protected void updatePinnedMode() { boolean hasPinnedNotification = hasPinnedNotificationInternal(); if (hasPinnedNotification == mHasPinnedNotification) { return; } + if (DEBUG) { + Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " + + hasPinnedNotification); + } mHasPinnedNotification = hasPinnedNotification; if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } - updateTouchableRegionListener(); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } @@ -285,47 +216,36 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL * @return true if the notification was removed and false if it still needs to be kept around * for a bit since it wasn't shown long enough */ - public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) { - if (DEBUG) Log.v(TAG, "remove"); - if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { - releaseImmediately(key); - return true; - } else { - getHeadsUpEntry(key).removeAsSoonAsPossible(); - return false; - } - } - - private boolean wasShownLongEnough(String key) { - HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); - HeadsUpEntry topEntry = getTopEntry(); - if (mSwipedOutKeys.contains(key)) { - // We always instantly dismiss views being manually swiped out. - mSwipedOutKeys.remove(key); - return true; - } - if (headsUpEntry != topEntry) { - return true; - } - return headsUpEntry.wasShownLongEnough(); + public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { + if (DEBUG) Log.v(TAG, "removeNotification"); + releaseImmediately(key); + return true; } + /** + * Returns if the given notification is in the Heads Up Notification list or not. + */ public boolean isHeadsUp(String key) { return mHeadsUpEntries.containsKey(key); } /** - * Push any current Heads Up notification down into the shade. + * Pushes any current Heads Up notification down into the shade. */ public void releaseAllImmediately() { if (DEBUG) Log.v(TAG, "releaseAllImmediately"); - ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet()); - for (String key : keys) { - releaseImmediately(key); + Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator(); + while (iterator.hasNext()) { + HeadsUpEntry entry = iterator.next(); + iterator.remove(); + onHeadsUpEntryRemoved(entry); } } - public void releaseImmediately(String key) { + /** + * Pushes the given Heads Up notification down into the shade. + */ + public void releaseImmediately(@NonNull String key) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); if (headsUpEntry == null) { return; @@ -334,11 +254,14 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL removeHeadsUpEntry(shadeEntry); } - public boolean isSnoozed(String packageName) { + /** + * Returns if the given notification is snoozed or not. + */ + public boolean isSnoozed(@NonNull String packageName) { final String key = snoozeKey(packageName, mUser); Long snoozedUntil = mSnoozedPackages.get(key); if (snoozedUntil != null) { - if (snoozedUntil > SystemClock.elapsedRealtime()) { + if (snoozedUntil > mClock.currentTimeMillis()) { if (DEBUG) Log.v(TAG, key + " snoozed"); return true; } @@ -347,33 +270,61 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return false; } + /** + * Snoozes all current Heads Up Notifications. + */ public void snooze() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); String packageName = entry.entry.notification.getPackageName(); mSnoozedPackages.put(snoozeKey(packageName, mUser), - SystemClock.elapsedRealtime() + mSnoozeLengthMs); + mClock.currentTimeMillis() + mSnoozeLengthMs); } - mReleaseOnExpandFinish = true; } - private static String snoozeKey(String packageName, int user) { + private static String snoozeKey(@NonNull String packageName, int user) { return user + "," + packageName; } - private HeadsUpEntry getHeadsUpEntry(String key) { + protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { return mHeadsUpEntries.get(key); } - public NotificationData.Entry getEntry(String key) { - return mHeadsUpEntries.get(key).entry; + /** + * Returns the entry of given Heads Up Notification. + * + * @param key Key of heads up notification + */ + public NotificationData.Entry getEntry(@NonNull String key) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + return entry != null ? entry.entry : null; + } + + /** + * Returns the stream of all current Heads Up Notifications. + */ + @NonNull + public Stream<NotificationData.Entry> getAllEntries() { + return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry); + } + + /** + * Returns the top Heads Up Notification, which appeares to show at first. + */ + @Nullable + public NotificationData.Entry getTopEntry() { + HeadsUpEntry topEntry = getTopHeadsUpEntry(); + return (topEntry != null) ? topEntry.entry : null; } - public Collection<HeadsUpEntry> getAllEntries() { - return mHeadsUpEntries.values(); + /** + * Returns if any heads up notification is available or not. + */ + public boolean hasHeadsUpNotifications() { + return !mHeadsUpEntries.isEmpty(); } - public HeadsUpEntry getTopEntry() { + protected HeadsUpEntry getTopHeadsUpEntry() { if (mHeadsUpEntries.isEmpty()) { return null; } @@ -387,56 +338,21 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Decides whether a click is invalid for a notification, i.e it has not been shown long enough - * that a user might have consciously clicked on it. - * - * @param key the key of the touched notification - * @return whether the touch is invalid and should be discarded + * Sets the current user. */ - public boolean shouldSwallowClick(String key) { - HeadsUpEntry entry = mHeadsUpEntries.get(key); - if (entry != null && mClock.currentTimeMillis() < entry.postTime) { - return true; - } - return false; - } - - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsExpanded || mBar.isBouncerShowing()) { - // The touchable region is always the full area when expanded - return; - } - if (mHasPinnedNotification) { - ExpandableNotificationRow topEntry = getTopEntry().entry.row; - if (topEntry.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); - if (groupSummary != null) { - topEntry = groupSummary; - } - } - topEntry.getLocationOnScreen(mTmpTwoArray); - int minX = mTmpTwoArray[0]; - int maxX = mTmpTwoArray[0] + topEntry.getWidth(); - int maxY = topEntry.getIntrinsicHeight(); - - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(minX, 0, maxX, maxY); - } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); - } - } - public void setUser(int user) { mUser = user; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HeadsUpManager state:"); + dumpInternal(fd, pw, args); + } + + protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); - pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); + pw.print(" now="); pw.println(mClock.currentTimeMillis()); pw.print(" mUser="); pw.println(mUser); for (HeadsUpEntry entry: mHeadsUpEntries.values()) { pw.print(" HeadsUpEntry="); pw.println(entry.entry); @@ -449,6 +365,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } + /** + * Returns if there are any pinned Heads Up Notifications or not. + */ public boolean hasPinnedHeadsUp() { return mHasPinnedNotification; } @@ -464,14 +383,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Notifies that a notification was swiped out and will be removed. - * - * @param key the notification key + * Unpins all pinned Heads Up Notifications. */ - public void addSwipedOutNotification(String key) { - mSwipedOutKeys.add(key); - } - public void unpinAll() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); @@ -481,60 +394,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - public void onExpandingFinished() { - if (mReleaseOnExpandFinish) { - releaseAllImmediately(); - mReleaseOnExpandFinish = false; - } else { - for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } - } - } - mEntriesToRemoveAfterExpand.clear(); - } - - public void setTrackingHeadsUp(boolean trackingHeadsUp) { - mTrackingHeadsUp = trackingHeadsUp; - } - - public boolean isTrackingHeadsUp() { - return mTrackingHeadsUp; - } - - public void setIsExpanded(boolean isExpanded) { - if (isExpanded != mIsExpanded) { - mIsExpanded = isExpanded; - if (isExpanded) { - // make sure our state is sane - mWaitingOnCollapseWhenGoingAway = false; - mHeadsUpGoingAway = false; - updateTouchableRegionListener(); - } - } - } - /** - * @return the height of the top heads up notification when pinned. This is different from the - * intrinsic height, which also includes whether the notification is system expanded and - * is mainly used when dragging down from a heads up notification. + * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as + * well. */ - public int getTopHeadsUpPinnedHeight() { - HeadsUpEntry topEntry = getTopEntry(); - if (topEntry == null || topEntry.entry == null) { - return 0; - } - ExpandableNotificationRow row = topEntry.entry.row; - if (row.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(row.getStatusBarNotification()); - if (groupSummary != null) { - row = groupSummary; - } - } - return row.getPinnedHeadsUpHeight(); + public boolean isTrackingHeadsUp() { + // Might be implemented in subclass. + return false; } /** @@ -553,147 +419,67 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Set that we are exiting the headsUp pinned mode, but some notifications might still be - * animating out. This is used to keep the touchable regions in a sane state. + * Sets an entry to be expanded and therefore stick in the heads up area if it's pinned + * until it's collapsed again. */ - public void setHeadsUpGoingAway(boolean headsUpGoingAway) { - if (headsUpGoingAway != mHeadsUpGoingAway) { - mHeadsUpGoingAway = headsUpGoingAway; - if (!headsUpGoingAway) { - waitForStatusBarLayout(); - } - updateTouchableRegionListener(); - } - } - - /** - * We need to wait on the whole panel to collapse, before we can remove the touchable region - * listener. - */ - private void waitForStatusBarLayout() { - mWaitingOnCollapseWhenGoingAway = true; - mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { - mStatusBarWindowView.removeOnLayoutChangeListener(this); - mWaitingOnCollapseWhenGoingAway = false; - updateTouchableRegionListener(); - } - } - }); - } - - public static void setIsClickedNotification(View child, boolean clicked) { - child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); - } - - public static boolean isClickedHeadsUpNotification(View child) { - Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION); - return clicked != null && clicked; - } - - public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { - headsUpEntry.remoteInputActive = remoteInputActive; - if (remoteInputActive) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } /** * Set an entry to be expanded and therefore stick in the heads up area if it's pinned * until it's collapsed again. */ - public void setExpanded(NotificationData.Entry entry, boolean expanded) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) { - headsUpEntry.expanded = expanded; - if (expanded) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } - - @Override - public void onReorderingAllowed() { - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); - for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } + public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) { + HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); + if (headsUpEntry != null && entry.row.isPinned()) { + headsUpEntry.expanded(expanded); } - mEntriesToRemoveWhenReorderingAllowed.clear(); - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); } - public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) { - mVisualStabilityManager = visualStabilityManager; + @NonNull + protected HeadsUpEntry createHeadsUpEntry() { + return new HeadsUpEntry(); } - public void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; + protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) { + // Do nothing for HeadsUpEntry. } /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. */ - public class HeadsUpEntry implements Comparable<HeadsUpEntry> { - public NotificationData.Entry entry; + protected class HeadsUpEntry implements Comparable<HeadsUpEntry> { + @Nullable public NotificationData.Entry entry; public long postTime; - public long earliestRemovaltime; - private Runnable mRemoveHeadsUpRunnable; public boolean remoteInputActive; + public long earliestRemovaltime; public boolean expanded; - public void setEntry(final NotificationData.Entry entry) { + private Runnable mRemoveHeadsUpRunnable; + + public void setEntry(@Nullable final NotificationData.Entry entry) { + setEntry(entry, null); + } + + public void setEntry(@Nullable final NotificationData.Entry entry, + @Nullable Runnable removeHeadsUpRunnable) { this.entry = entry; + this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable; // The actual post time will be just after the heads-up really slided in postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay; - mRemoveHeadsUpRunnable = new Runnable() { - @Override - public void run() { - if (!mVisualStabilityManager.isReorderingAllowed()) { - mEntriesToRemoveWhenReorderingAllowed.add(entry); - mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this); - } else if (!mTrackingHeadsUp) { - removeHeadsUpEntry(entry); - } else { - mEntriesToRemoveAfterExpand.add(entry); - } - } - }; - updateEntry(); - } - - public void updateEntry() { - updateEntry(true); + updateEntry(true /* updatePostTime */); } public void updateEntry(boolean updatePostTime) { + if (DEBUG) Log.v(TAG, "updateEntry"); + long currentTime = mClock.currentTimeMillis(); earliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { postTime = Math.max(postTime, currentTime); } removeAutoRemovalCallbacks(); - if (mEntriesToRemoveAfterExpand.contains(entry)) { - mEntriesToRemoveAfterExpand.remove(entry); - } - if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { - mEntriesToRemoveWhenReorderingAllowed.remove(entry); - } + if (!isSticky()) { long finishTime = postTime + mHeadsUpNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); @@ -707,7 +493,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } @Override - public int compareTo(HeadsUpEntry o) { + public int compareTo(@NonNull HeadsUpEntry o) { boolean isPinned = entry.row.isPinned(); boolean otherPinned = o.entry.row.isPinned(); if (isPinned && !otherPinned) { @@ -734,26 +520,29 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL : -1; } - public void removeAutoRemovalCallbacks() { - mHandler.removeCallbacks(mRemoveHeadsUpRunnable); - } - - public boolean wasShownLongEnough() { - return earliestRemovaltime < mClock.currentTimeMillis(); - } - - public void removeAsSoonAsPossible() { - removeAutoRemovalCallbacks(); - mHandler.postDelayed(mRemoveHeadsUpRunnable, - earliestRemovaltime - mClock.currentTimeMillis()); + public void expanded(boolean expanded) { + this.expanded = expanded; } public void reset() { - removeAutoRemovalCallbacks(); entry = null; - mRemoveHeadsUpRunnable = null; expanded = false; remoteInputActive = false; + removeAutoRemovalCallbacks(); + mRemoveHeadsUpRunnable = null; + } + + public void removeAutoRemovalCallbacks() { + if (mRemoveHeadsUpRunnable != null) + mHandler.removeCallbacks(mRemoveHeadsUpRunnable); + } + + public void removeAsSoonAsPossible() { + if (mRemoveHeadsUpRunnable != null) { + removeAutoRemovalCallbacks(); + mHandler.postDelayed(mRemoveHeadsUpRunnable, + earliestRemovaltime - mClock.currentTimeMillis()); + } } } @@ -762,5 +551,4 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return SystemClock.elapsedRealtime(); } } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java new file mode 100644 index 000000000000..1e3c123cfbc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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.policy; + +import android.view.View; + +import com.android.systemui.R; + +/** + * A class of utility static methods for heads up notifications. + */ +public final class HeadsUpUtil { + private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; + + /** + * Set the given view as clicked or not-clicked. + * @param view The view to be set the flag to. + * @param clicked True to set as clicked. False to not-clicked. + */ + public static void setIsClickedHeadsUpNotification(View view, boolean clicked) { + view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); + } + + /** + * Check if the given view has the flag of "clicked notification" + * @param view The view to be checked. + * @return True if the view has clicked. False othrewise. + */ + public static boolean isClickedHeadsUpNotification(View view) { + Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION); + return clicked != null && clicked; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 424858a86e58..d7a810eca02e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -64,7 +64,7 @@ public class AmbientState { private boolean mPanelTracking; private boolean mExpansionChanging; private boolean mPanelFullWidth; - private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; + private boolean mPulsing; private boolean mUnlockHintRunning; private boolean mQsCustomizerShowing; private int mIntrinsicPadding; @@ -315,23 +315,18 @@ public class AmbientState { } public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) { + public void setPulsing(boolean hasPulsing) { mPulsing = hasPulsing; } public boolean isPulsing(NotificationData.Entry entry) { - if (mPulsing == null) { + if (!mPulsing || mHeadsUpManager == null) { return false; } - for (HeadsUpManager.HeadsUpEntry e : mPulsing) { - if (e.entry == entry) { - return true; - } - } - return false; + return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry)); } public boolean isPanelTracking() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index ad8a0eb98ead..6d20684ec3fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -92,10 +92,11 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; import android.support.v4.graphics.ColorUtils; @@ -288,7 +289,7 @@ public class NotificationStackScrollLayout extends ViewGroup private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>(); private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations = new HashSet<>(); - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; @@ -358,7 +359,7 @@ public class NotificationStackScrollLayout extends ViewGroup } }; private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); - private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; + private boolean mPulsing; private boolean mDrawBackgroundAsSrc; private boolean mFadingOut; private boolean mParentNotFullyVisible; @@ -689,7 +690,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - if (mPulsing != null) { + if (mPulsing) { mTopPadding = mClockBottom; } else { mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding; @@ -919,6 +920,27 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * @return the height of the top heads up notification when pinned. This is different from the + * intrinsic height, which also includes whether the notification is system expanded and + * is mainly used when dragging down from a heads up notification. + */ + private int getTopHeadsUpPinnedHeight() { + NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); + if (topEntry == null) { + return 0; + } + ExpandableNotificationRow row = topEntry.row; + if (row.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(row.getStatusBarNotification()); + if (groupSummary != null) { + row = groupSummary; + } + } + return row.getPinnedHeadsUpHeight(); + } + + /** * @return the position from where the appear transition ends when expanding. * Measured in absolute height. */ @@ -929,7 +951,7 @@ public class NotificationStackScrollLayout extends ViewGroup int minNotificationsForShelf = 1; if (mTrackingHeadsUp || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { - appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight(); + appearPosition = getTopHeadsUpPinnedHeight(); minNotificationsForShelf = 2; } else { appearPosition = 0; @@ -1197,9 +1219,9 @@ public class NotificationStackScrollLayout extends ViewGroup if (slidingChild instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; if (!mIsExpanded && row.isHeadsUp() && row.isPinned() - && mHeadsUpManager.getTopEntry().entry.row != row + && mHeadsUpManager.getTopEntry().row != row && mGroupManager.getGroupSummary( - mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification()) + mHeadsUpManager.getTopEntry().row.getStatusBarNotification()) != row) { continue; } @@ -2119,7 +2141,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } private void updateScrollability() { @@ -2751,7 +2773,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private boolean isClickedHeadsUp(View child) { - return HeadsUpManager.isClickedHeadsUpNotification(child); + return HeadsUpUtil.isClickedHeadsUpNotification(child); } /** @@ -4256,7 +4278,7 @@ public class NotificationStackScrollLayout extends ViewGroup mAnimationFinishedRunnables.add(runnable); } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; mAmbientState.setHeadsUpManager(headsUpManager); } @@ -4324,8 +4346,8 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsExpanded; } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) { - if (mPulsing == null && pulsing == null) { + public void setPulsing(boolean pulsing, int clockBottom) { + if (!mPulsing && !pulsing) { return; } mPulsing = pulsing; @@ -4463,7 +4485,7 @@ public class NotificationStackScrollLayout extends ViewGroup pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" + " alpha:%f scrollY:%d]", this.getClass().getSimpleName(), - mPulsing != null ?"T":"f", + mPulsing ? "T":"f", mAmbientState.isQsCustomizerShowing() ? "T":"f", getVisibility() == View.VISIBLE ? "visible" : getVisibility() == View.GONE ? "gone" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index 682b8493e913..04a7bd79c6ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -30,7 +30,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; /** * A state of a view. This can be used to apply a set of view properties to a view with @@ -582,7 +582,7 @@ public class ViewState { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - HeadsUpManager.setIsClickedNotification(child, false); + HeadsUpUtil.setIsClickedHeadsUpNotification(child, false); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); child.setTag(TAG_START_TRANSLATION_Y, null); child.setTag(TAG_END_TRANSLATION_Y, null); 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 6e7477fbac38..f3c1171f650c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationInflaterTest; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -51,7 +52,7 @@ public class NotificationTestHelper { public NotificationTestHelper(Context context) { mContext = context; mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager); + mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null); } public ExpandableNotificationRow createRow() throws Exception { 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 new file mode 100644 index 000000000000..28f941779043 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.app.ActivityManager; +import android.app.Notification; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.view.View; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.notification.VisualStabilityManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class HeadsUpManagerPhoneTest extends SysuiTestCase { + @Rule public MockitoRule rule = MockitoJUnit.rule(); + + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + + private HeadsUpManagerPhone mHeadsUpManager; + + private NotificationData.Entry mEntry; + private StatusBarNotification mSbn; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @Mock private NotificationGroupManager mGroupManager; + @Mock private View mStatusBarWindowView; + @Mock private StatusBar mBar; + @Mock private ExpandableNotificationRow mRow; + @Mock private VisualStabilityManager mVSManager; + + @Before + public void setUp() { + when(mVSManager.isReorderingAllowed()).thenReturn(true); + + mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager); + + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0); + + mEntry = new NotificationData.Entry(mSbn); + mEntry.row = mRow; + mEntry.expandedIcon = mock(StatusBarIconView.class); + } + + @Test + public void testBasicOperations() { + // Check the initial state. + assertNull(mHeadsUpManager.getEntry(mEntry.key)); + assertNull(mHeadsUpManager.getTopEntry()); + assertEquals(0, mHeadsUpManager.getAllEntries().count()); + assertFalse(mHeadsUpManager.hasHeadsUpNotifications()); + + // Add a notification. + mHeadsUpManager.showNotification(mEntry); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + + // Update the notification. + mHeadsUpManager.updateNotification(mEntry, false); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + + // Remove but defer, since the notification is visible on display. + mHeadsUpManager.removeNotification(mEntry.key, false); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + } +} 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 bdf9b1f6da9e..31442af5a04c 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 @@ -86,8 +86,8 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -110,7 +110,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private UnlockMethodCache mUnlockMethodCache; @Mock private KeyguardIndicationController mKeyguardIndicationController; @Mock private NotificationStackScrollLayout mStackScroller; - @Mock private HeadsUpManager mHeadsUpManager; + @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private SystemServicesProxy mSystemServicesProxy; @Mock private NotificationPanelView mNotificationPanelView; @Mock private IStatusBarService mBarService; @@ -588,7 +588,7 @@ public class StatusBarTest extends SysuiTestCase { static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, - NotificationStackScrollLayout stack, HeadsUpManager hum, + NotificationStackScrollLayout stack, HeadsUpManagerPhone hum, PowerManager pm, NotificationPanelView panelView, IStatusBarService barService, NotificationListener notificationListener, NotificationLogger notificationLogger, @@ -650,7 +650,7 @@ public class StatusBarTest extends SysuiTestCase { public void setUpForTest(NotificationPresenter presenter, NotificationListContainer listContainer, Callback callback, - HeadsUpManager headsUpManager, + HeadsUpManagerPhone headsUpManager, NotificationData notificationData) { super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager); mNotificationData = notificationData; |