diff options
8 files changed, 430 insertions, 286 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 665df777c1c1..03324777e4ea 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -16,47 +16,65 @@ package com.android.systemui.bubbles; +import android.os.UserHandle; import android.view.LayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import java.util.Objects; + /** * Encapsulates the data and UI elements of a bubble. */ class Bubble { + private static final boolean DEBUG = false; + private static final String TAG = "Bubble"; + private final String mKey; + private final String mGroupId; private final BubbleExpandedView.OnBubbleBlockedListener mListener; private boolean mInflated; - - public BubbleView iconView; - public BubbleExpandedView expandedView; public NotificationEntry entry; + BubbleView iconView; + BubbleExpandedView expandedView; + + private static String groupId(NotificationEntry entry) { + UserHandle user = entry.notification.getUser(); + return user.getIdentifier() + '|' + entry.notification.getPackageName(); + } Bubble(NotificationEntry e, BubbleExpandedView.OnBubbleBlockedListener listener) { entry = e; mKey = e.key; + mGroupId = groupId(e); mListener = listener; } - /** @deprecated use the other constructor to defer View creation. */ - @Deprecated - Bubble(NotificationEntry e, LayoutInflater inflater, BubbleStackView stackView, - BubbleExpandedView.OnBubbleBlockedListener listener) { - this(e, listener); - inflate(inflater, stackView); - } - public String getKey() { return mKey; } + public String getGroupId() { + return mGroupId; + } + + public String getPackageName() { + return entry.notification.getPackageName(); + } + boolean isInflated() { return mInflated; } + public void updateDotVisibility() { + if (iconView != null) { + iconView.updateDotVisibility(); + } + } + void inflate(LayoutInflater inflater, BubbleStackView stackView) { if (mInflated) { return; @@ -73,10 +91,32 @@ class Bubble { mInflated = true; } + void setDismissed() { + entry.setBubbleDismissed(true); + // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker + if (expandedView != null) { + expandedView.cleanUpExpandedState(); + } + } + void setEntry(NotificationEntry entry) { + this.entry = entry; if (mInflated) { iconView.update(entry); expandedView.update(entry); } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Bubble)) return false; + Bubble bubble = (Bubble) o; + return Objects.equals(mKey, bubble.mKey); + } + + @Override + public int hashCode() { + return Objects.hash(mKey); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 0fcc9501e367..acdcfb2ea688 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -77,8 +77,7 @@ import javax.inject.Singleton; * The controller manages addition, removal, and visible state of bubbles on screen. */ @Singleton -public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListener, - ConfigurationController.ConfigurationListener { +public class BubbleController implements ConfigurationController.ConfigurationListener { private static final String TAG = "BubbleController"; @@ -174,6 +173,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe @Override public void onStateChanged(int newState) { mState = newState; + boolean shouldCollapse = (mState != SHADE); + if (shouldCollapse) { + collapseStack(); + } updateVisibility(); } } @@ -236,7 +239,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } - mStackView.setOnBlockedListener(this); } } @@ -284,28 +286,38 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe if (mStackView == null) { return false; } - for (Bubble bubble : mBubbleData.getBubbles()) { - if (!bubble.entry.isBubbleDismissed()) { - return true; - } - } - return false; + return mBubbleData.hasBubbles(); } /** * Whether the stack of bubbles is expanded or not. */ public boolean isStackExpanded() { - return mStackView != null && mStackView.isExpanded(); + return mBubbleData.isExpanded(); + } + + /** + * Tell the stack of bubbles to expand. + */ + public void expandStack() { + mBubbleData.setExpanded(true); } /** * Tell the stack of bubbles to collapse. */ public void collapseStack() { - if (mStackView != null) { - mStackView.collapseStack(); - } + mBubbleData.setExpanded(false /* expanded */); + } + + void selectBubble(Bubble bubble) { + mBubbleData.setSelectedBubble(bubble); + } + + @VisibleForTesting + void selectBubble(String key) { + Bubble bubble = mBubbleData.getBubbleWithKey(key); + selectBubble(bubble); } /** @@ -314,8 +326,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * @param notificationKey the notification key for the bubble to be selected */ public void expandStackAndSelectBubble(String notificationKey) { - if (mStackView != null && mBubbleData.getBubble(notificationKey) != null) { - mStackView.setExpandedBubble(notificationKey); + Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey); + if (bubble != null) { + mBubbleData.setSelectedBubble(bubble); + mBubbleData.setExpanded(true); } } @@ -323,13 +337,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack. */ void dismissStack(@DismissReason int reason) { - if (mStackView == null) { - return; - } - mStackView.stackDismissed(reason); - - updateVisibility(); - mNotificationEntryManager.updateNotifications(); + mBubbleData.dismissAll(reason); } /** @@ -348,20 +356,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * @param notif the notification associated with this bubble. */ void updateBubble(NotificationEntry notif) { - if (mStackView != null && mBubbleData.getBubble(notif.key) != null) { - // It's an update - mStackView.updateBubble(notif); - } else { - // It's new - ensureStackViewCreated(); - mStackView.addBubble(notif); - } - Bubble bubble = mBubbleData.getBubble(notif.key); - if (shouldAutoExpand(notif)) { - mStackView.setSelectedBubble(bubble); - mStackView.setExpanded(true); - } - updateVisibility(); + mBubbleData.notificationEntryUpdated(notif); } /** @@ -371,23 +366,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe */ @MainThread void removeBubble(String key, int reason) { - if (mStackView != null) { - mStackView.removeBubble(key, reason); - } - mNotificationEntryManager.updateNotifications(); - updateVisibility(); - } - - @Override - public void onBubbleBlocked(NotificationEntry entry) { - Object[] bubbles = mBubbleData.getBubbles().toArray(); - for (int i = 0; i < bubbles.length; i++) { - NotificationEntry e = ((Bubble) bubbles[i]).entry; - boolean samePackage = entry.notification.getPackageName().equals( - e.notification.getPackageName()); - if (samePackage) { - removeBubble(entry.key, DISMISS_BLOCKED); - } + // TEMP: refactor to change this to pass entry + Bubble bubble = mBubbleData.getBubbleWithKey(key); + if (bubble != null) { + mBubbleData.notificationEntryRemoved(bubble.entry, reason); } } @@ -424,7 +406,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe updateShowInShadeForSuppressNotification(entry); entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed updateBubble(entry); - mStackView.updateDotVisibility(entry.key); } } @@ -446,44 +427,57 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe } }; + @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { + @Override public void onBubbleAdded(Bubble bubble) { - + ensureStackViewCreated(); + mStackView.addBubble(bubble); } @Override public void onBubbleRemoved(Bubble bubble, int reason) { - + if (mStackView != null) { + mStackView.removeBubble(bubble); + } } public void onBubbleUpdated(Bubble bubble) { - + if (mStackView != null) { + mStackView.updateBubble(bubble); + } } @Override public void onOrderChanged(List<Bubble> bubbles) { - } @Override public void onSelectionChanged(Bubble selectedBubble) { - + if (mStackView != null) { + mStackView.setSelectedBubble(selectedBubble); + } } @Override public void onExpandedChanged(boolean expanded) { - + if (mStackView != null) { + mStackView.setExpanded(expanded); + } } @Override public void showFlyoutText(Bubble bubble, String text) { - + if (mStackView != null) { + mStackView.animateInFlyoutForBubble(bubble); + } } @Override public void apply() { - + mNotificationEntryManager.updateNotifications(); + updateVisibility(); } }; @@ -514,7 +508,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE); } else if (mStackView != null) { mStackView.setVisibility(INVISIBLE); - collapseStack(); } updateBubblesShowing(); } @@ -621,14 +614,14 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe @Override public void onTaskMovedToFront(RunningTaskInfo taskInfo) { if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) { - mStackView.collapseStack(); + mBubbleData.setExpanded(false); } } @Override public void onActivityLaunchOnSecondaryDisplayRerouted() { if (mStackView != null) { - mStackView.collapseStack(); + mBubbleData.setExpanded(false); } } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index fe3f9d192cd5..259665dedf5b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -15,14 +15,24 @@ */ package com.android.systemui.bubbles; -import androidx.annotation.Nullable; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; + +import android.app.ActivityManager; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.bubbles.BubbleController.DismissReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; @@ -33,6 +43,8 @@ import javax.inject.Singleton; @Singleton public class BubbleData { + private static final String TAG = "BubbleData"; + /** * This interface reports changes to the state and appearance of bubbles which should be applied * as necessary to the UI. @@ -53,7 +65,7 @@ public class BubbleData { * A Bubble has been removed. A call to {@link #onOrderChanged(List)} will * follow. */ - void onBubbleRemoved(Bubble bubble, @BubbleController.DismissReason int reason); + void onBubbleRemoved(Bubble bubble, @DismissReason int reason); /** * An existing bubble has been updated. @@ -86,46 +98,253 @@ public class BubbleData { void apply(); } - private HashMap<String, Bubble> mBubbles = new HashMap<>(); + private final Context mContext; + private final List<Bubble> mBubbles = new ArrayList<>(); + private Bubble mSelectedBubble; + private boolean mExpanded; private Listener mListener; @VisibleForTesting @Inject - public BubbleData() {} + public BubbleData(Context context) { + mContext = context; + } + + public boolean hasBubbles() { + return !mBubbles.isEmpty(); + } + + public boolean isExpanded() { + return mExpanded; + } + + public boolean hasBubbleWithKey(String key) { + return getBubbleWithKey(key) != null; + } + + public void setExpanded(boolean expanded) { + if (setExpandedInternal(expanded)) { + mListener.apply(); + } + } + + public void setSelectedBubble(Bubble bubble) { + if (setSelectedBubbleInternal(bubble)) { + mListener.apply(); + } + } + + public void notificationEntryUpdated(NotificationEntry entry) { + Bubble bubble = getBubbleWithKey(entry.key); + if (bubble == null) { + // Create a new bubble + bubble = new Bubble(entry, this::onBubbleBlocked); + mBubbles.add(0, bubble); // TODO: reorder/group + mListener.onBubbleAdded(bubble); + } else { + // Updates an existing bubble + bubble.setEntry(entry); + mListener.onBubbleUpdated(bubble); + } + if (shouldAutoExpand(entry)) { + setSelectedBubbleInternal(bubble); + if (!mExpanded) { + setExpandedInternal(true); + } + } else if (mSelectedBubble == null) { + setSelectedBubbleInternal(bubble); + } + // TODO: reorder/group + mListener.apply(); + } + + public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { + int indexToRemove = indexForKey(entry.key); + if (indexToRemove >= 0) { + Bubble removed = mBubbles.remove(indexToRemove); + removed.setDismissed(); + mListener.onBubbleRemoved(removed, reason); + maybeSendDeleteIntent(reason, removed.entry); + + if (mBubbles.isEmpty()) { + setExpandedInternal(false); + setSelectedBubbleInternal(null); + } else if (removed == mSelectedBubble) { + int newIndex = Math.min(indexToRemove, mBubbles.size() - 1); + Bubble newSelected = mBubbles.get(newIndex); + setSelectedBubbleInternal(newSelected); + } + // TODO: reorder/group + mListener.apply(); + } + } + + public void dismissAll(@DismissReason int reason) { + boolean changed = setExpandedInternal(false); + while (!mBubbles.isEmpty()) { + Bubble bubble = mBubbles.remove(0); + bubble.setDismissed(); + maybeSendDeleteIntent(reason, bubble.entry); + mListener.onBubbleRemoved(bubble, reason); + changed = true; + } + if (setSelectedBubbleInternal(null)) { + changed = true; + } + if (changed) { + // TODO: reorder/group + mListener.apply(); + } + } /** - * The set of bubbles. + * Requests a change to the selected bubble. Calls {@link Listener#onSelectionChanged} if + * the value changes. + * + * @param bubble the new selected bubble + * @return true if the state changed as a result */ - public Collection<Bubble> getBubbles() { - return mBubbles.values(); + private boolean setSelectedBubbleInternal(Bubble bubble) { + if (Objects.equals(bubble, mSelectedBubble)) { + return false; + } + if (bubble != null && !mBubbles.contains(bubble)) { + Log.e(TAG, "Cannot select bubble which doesn't exist!" + + " (" + bubble + ") bubbles=" + mBubbles); + return false; + } + if (mExpanded) { + // TODO: bubble.markAsActive() ? + bubble.entry.setShowInShadeWhenBubble(false); + } + mSelectedBubble = bubble; + mListener.onSelectionChanged(mSelectedBubble); + return true; + } + + + /** + * Requests a change to the expanded state. Calls {@link Listener#onExpandedChanged} if + * the value changes. + * + * @param shouldExpand the new requested state + * @return true if the state changed as a result + */ + private boolean setExpandedInternal(boolean shouldExpand) { + if (mExpanded == shouldExpand) { + return false; + } + if (shouldExpand) { + if (mBubbles.isEmpty()) { + Log.e(TAG, "Attempt to expand stack when empty!"); + return false; + } + if (mSelectedBubble == null) { + Log.e(TAG, "Attempt to expand stack without selected bubble!"); + return false; + } + // TODO: bubble.markAsActive() ? + mSelectedBubble.entry.setShowInShadeWhenBubble(false); + } + // TODO: reorder/regroup + mExpanded = shouldExpand; + mListener.onExpandedChanged(mExpanded); + return true; } - @Nullable - public Bubble getBubble(String key) { - return mBubbles.get(key); + private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) { + if (reason == BubbleController.DISMISS_USER_GESTURE) { + Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); + PendingIntent deleteIntent = bubbleMetadata != null + ? bubbleMetadata.getDeleteIntent() + : null; + if (deleteIntent != null) { + try { + deleteIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to send delete intent for bubble with key: " + entry.key); + } + } + } } - public void addBubble(Bubble b) { - mBubbles.put(b.getKey(), b); + private void onBubbleBlocked(NotificationEntry entry) { + boolean changed = false; + final String blockedPackage = entry.notification.getPackageName(); + for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) { + Bubble bubble = i.next(); + if (bubble.getPackageName().equals(blockedPackage)) { + i.remove(); + mListener.onBubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED); + changed = true; + } + } + if (changed) { + // TODO: reorder/group + mListener.apply(); + } } - @Nullable - public Bubble removeBubble(String key) { - return mBubbles.remove(key); + private int indexForKey(String key) { + for (int i = 0; i < mBubbles.size(); i++) { + Bubble bubble = mBubbles.get(i); + if (bubble.getKey().equals(key)) { + return i; + } + } + return -1; } - public void updateBubble(String key, NotificationEntry newEntry) { - Bubble oldBubble = mBubbles.get(key); - if (oldBubble != null) { - oldBubble.setEntry(newEntry); + private Bubble removeBubbleWithKey(String key) { + for (int i = 0; i < mBubbles.size(); i++) { + Bubble bubble = mBubbles.get(i); + if (bubble.getKey().equals(key)) { + mBubbles.remove(i); + return bubble; + } } + return null; + } + + /** + * The set of bubbles. + * + * @deprecated + */ + @Deprecated + public Collection<Bubble> getBubbles() { + return Collections.unmodifiableList(mBubbles); } - public void clear() { - mBubbles.clear(); + @VisibleForTesting(visibility = PRIVATE) + Bubble getBubbleWithKey(String key) { + for (int i = 0; i < mBubbles.size(); i++) { + Bubble bubble = mBubbles.get(i); + if (bubble.getKey().equals(key)) { + return bubble; + } + } + return null; } public void setListener(Listener listener) { mListener = listener; } -} + + boolean shouldAutoExpand(NotificationEntry entry) { + Notification.BubbleMetadata metadata = entry.getBubbleMetadata(); + return metadata != null && metadata.getAutoExpandBubble() + && isForegroundApp(entry.notification.getPackageName()); + } + + /** + * Return true if the applications with the package name is running in foreground. + * + * @param pkgName application package name. + */ + boolean isForegroundApp(String pkgName) { + ActivityManager am = mContext.getSystemService(ActivityManager.class); + List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */); + return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName()); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 424cd5544e50..18b2e37e31ec 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -20,8 +20,6 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.annotation.NonNull; -import android.app.Notification; -import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Outline; @@ -54,9 +52,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; -import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.bubbles.BubbleController.DismissReason; import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; @@ -359,14 +355,13 @@ public class BubbleStackView extends FrameLayout { } switch (action) { case AccessibilityNodeInfo.ACTION_DISMISS: - Dependency.get(BubbleController.class).dismissStack( - BubbleController.DISMISS_ACCESSIBILITY_ACTION); + mBubbleData.dismissAll(BubbleController.DISMISS_ACCESSIBILITY_ACTION); return true; case AccessibilityNodeInfo.ACTION_COLLAPSE: - collapseStack(); + mBubbleData.setExpanded(false); return true; case AccessibilityNodeInfo.ACTION_EXPAND: - expandStack(); + mBubbleData.setExpanded(true); return true; } return false; @@ -393,9 +388,9 @@ public class BubbleStackView extends FrameLayout { * @param key the {@link NotificationEntry#key} associated with the bubble. */ public void updateDotVisibility(String key) { - Bubble b = mBubbleData.getBubble(key); + Bubble b = mBubbleData.getBubbleWithKey(key); if (b != null) { - b.iconView.updateDotVisibility(); + b.updateDotVisibility(); } } @@ -407,16 +402,6 @@ public class BubbleStackView extends FrameLayout { } /** - * Sets the listener to notify when a bubble is blocked. - */ - public void setOnBlockedListener(BubbleExpandedView.OnBubbleBlockedListener listener) { - mBlockedListener = listener; - for (Bubble b : mBubbleData.getBubbles()) { - b.expandedView.setOnBlockedListener(mBlockedListener); - } - } - - /** * Whether the stack of bubbles is expanded or not. */ public boolean isExpanded() { @@ -445,7 +430,7 @@ public class BubbleStackView extends FrameLayout { */ @Deprecated void setExpandedBubble(String key) { - Bubble bubbleToExpand = mBubbleData.getBubble(key); + Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key); if (bubbleToExpand != null) { setSelectedBubble(bubbleToExpand); bubbleToExpand.entry.setShowInShadeWhenBubble(false); @@ -466,11 +451,36 @@ public class BubbleStackView extends FrameLayout { } } + // via BubbleData.Listener + void addBubble(Bubble bubble) { + bubble.inflate(mInflater, this); + mBubbleContainer.addView(bubble.iconView, 0, + new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters); + requestUpdate(); + logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); + } + + // via BubbleData.Listener + void removeBubble(Bubble bubble) { + // Remove it from the views + int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView); + mBubbleContainer.removeViewAt(removedIndex); + logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); + } + + // via BubbleData.Listener + void updateBubble(Bubble bubble) { + requestUpdate(); + logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED); + } + /** * Changes the currently selected bubble. If the stack is already expanded, the newly selected * bubble will be shown immediately. This does not change the expanded state or change the * position of any bubble. */ + // via BubbleData.Listener public void setSelectedBubble(Bubble bubbleToSelect) { if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) { return; @@ -489,7 +499,8 @@ public class BubbleStackView extends FrameLayout { logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); notifyExpansionChanged(previouslySelected.entry, false /* expanded */); - notifyExpansionChanged(bubbleToSelect.entry, true /* expanded */); + notifyExpansionChanged(bubbleToSelect == null ? null : bubbleToSelect.entry, + true /* expanded */); }); } } @@ -497,13 +508,15 @@ public class BubbleStackView extends FrameLayout { /** * Changes the expanded state of the stack. * - * @param expanded whether the bubble stack should appear expanded + * @param shouldExpand whether the bubble stack should appear expanded */ - public void setExpanded(boolean expanded) { - if (expanded == mIsExpanded) { + // via BubbleData.Listener + public void setExpanded(boolean shouldExpand) { + boolean wasExpanded = mIsExpanded; + if (shouldExpand == wasExpanded) { return; } - if (mIsExpanded) { + if (wasExpanded) { // Collapse the stack animateExpansion(false /* expand */); logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); @@ -518,131 +531,17 @@ public class BubbleStackView extends FrameLayout { } /** - * Adds a bubble to the top of the stack. - * - * @param entry the notification to add to the stack of bubbles. - */ - void addBubble(NotificationEntry entry) { - Bubble b = new Bubble(entry, mInflater, this /* stackView */, mBlockedListener); - mBubbleData.addBubble(b); - - mBubbleContainer.addView(b.iconView, 0, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - ViewClippingUtil.setClippingDeactivated(b.iconView, true, mClippingParameters); - - requestUpdate(); - logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); - - animateInFlyoutForBubble(b); - } - - /** - * Remove a bubble from the stack. - */ - void removeBubble(String key, int reason) { - Bubble b = mBubbleData.removeBubble(key); - if (b == null) { - return; - } - setBubbleDismissed(b, reason); - - // Remove it from the views - int removedIndex = mBubbleContainer.indexOfChild(b.iconView); - mBubbleContainer.removeViewAt(removedIndex); - - int bubbleCount = mBubbleContainer.getChildCount(); - if (bubbleCount == 0) { - // If no bubbles remain, collapse the entire stack. - collapseStack(); - return; - } else if (b.equals(mExpandedBubble)) { - // Was the current bubble just removed? - // If we have other bubbles and are expanded go to the next one or previous - // if the bubble removed was last - int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1; - BubbleView expandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex); - if (mIsExpanded) { - setExpandedBubble(expandedBubble.getKey()); - } else { - mExpandedBubble = null; - } - } - // TODO: consider logging reason code - logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); - } - - /** * Dismiss the stack of bubbles. + * @deprecated */ + @Deprecated void stackDismissed(int reason) { - for (Bubble bubble : mBubbleData.getBubbles()) { - setBubbleDismissed(bubble, reason); - } - mBubbleData.clear(); - collapseStack(); - mBubbleContainer.removeAllViews(); - mExpandedViewContainer.removeAllViews(); - // TODO: consider logging reason code + mBubbleData.dismissAll(reason); logBubbleEvent(null /* no bubble associated with bubble stack dismiss */, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED); } /** - * Marks the notification entry as dismissed & calls any delete intents for the bubble. - * - * <p>Note: This does not remove the Bubble from BubbleData. - * - * @param bubble the Bubble being dismissed - * @param reason code for the reason the dismiss was triggered - * @see BubbleController.DismissReason - */ - private void setBubbleDismissed(Bubble bubble, @DismissReason int reason) { - if (DEBUG) { - Log.d(TAG, "dismissBubble: " + bubble + " reason=" + reason); - } - bubble.entry.setBubbleDismissed(true); - bubble.expandedView.cleanUpExpandedState(); - - if (reason == BubbleController.DISMISS_USER_GESTURE) { - Notification.BubbleMetadata bubbleMetadata = bubble.entry.getBubbleMetadata(); - PendingIntent deleteIntent = bubbleMetadata != null - ? bubbleMetadata.getDeleteIntent() - : null; - if (deleteIntent != null) { - try { - deleteIntent.send(); - } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "Failed to send delete intent for bubble with key: " - + (bubble.entry != null ? bubble.entry.key : " null entry")); - } - } - } - } - - /** - * Updates a bubble in the stack. - * @param entry the entry to update in the stack. - */ - public void updateBubble(NotificationEntry entry) { - Bubble b = mBubbleData.getBubble(entry.key); - mBubbleData.updateBubble(entry.key, entry); - - if (!mIsExpanded) { - // If alerting it gets promoted to top of the stack. - if (mBubbleContainer.indexOfChild(b.iconView) != 0) { - mBubbleContainer.moveViewTo(b.iconView, 0); - } - requestUpdate(); - animateInFlyoutForBubble(b /* bubble */); - } - if (mIsExpanded && entry.equals(mExpandedBubble.entry)) { - entry.setShowInShadeWhenBubble(false); - requestUpdate(); - } - logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED); - } - - /** * @return the view the touch event is on */ @Nullable @@ -670,7 +569,7 @@ public class BubbleStackView extends FrameLayout { return this; } - public View getFlyoutView() { + View getFlyoutView() { return mFlyout; } @@ -683,13 +582,8 @@ public class BubbleStackView extends FrameLayout { */ @Deprecated @MainThread - public void collapseStack() { - if (mIsExpanded) { - // TODO: Save opened bubble & move it to top of stack - animateExpansion(false /* shouldExpand */); - notifyExpansionChanged(mExpandedBubble.entry, mIsExpanded); - logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); - } + void collapseStack() { + mBubbleData.setExpanded(false); } /** @@ -712,12 +606,8 @@ public class BubbleStackView extends FrameLayout { */ @Deprecated @MainThread - public void expandStack() { - if (!mIsExpanded) { - String expandedBubbleKey = getBubbleAt(0).getKey(); - setExpandedBubble(expandedBubbleKey); - logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); - } + void expandStack() { + mBubbleData.setExpanded(true); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 2e35f0686d55..97f2dd0749f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -22,7 +22,6 @@ import android.os.Trace; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.ViewParent; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleData; @@ -120,7 +119,7 @@ public class NotificationViewHierarchyManager { for (int i = 0; i < N; i++) { NotificationEntry ent = activeNotifications.get(i); if (ent.isRowDismissed() || ent.isRowRemoved() - || (mBubbleData.getBubble(ent.key) != null && !ent.showInShadeWhenBubble())) { + || (mBubbleData.hasBubbleWithKey(ent.key) && !ent.showInShadeWhenBubble())) { // we don't want to update removed notifications because they could // temporarily become children if they were isolated before. continue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 20f539bbebe5..40a53571ddf0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -146,7 +146,7 @@ public class BubbleControllerTest extends SysuiTestCase { when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel); - mBubbleData = new BubbleData(); + mBubbleData = new BubbleData(mContext); mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController, mBubbleData, mConfigurationController); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); @@ -177,25 +177,27 @@ public class BubbleControllerTest extends SysuiTestCase { public void testRemoveBubble() { mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); - + verify(mNotificationEntryManager).updateNotifications(); verify(mBubbleStateChangeListener).onHasBubblesChanged(true); mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE); assertFalse(mStatusBarWindowController.getBubblesShowing()); assertTrue(mRow.getEntry().isBubbleDismissed()); - verify(mNotificationEntryManager).updateNotifications(); + verify(mNotificationEntryManager, times(2)).updateNotifications(); verify(mBubbleStateChangeListener).onHasBubblesChanged(false); } @Test public void testDismissStack() { mBubbleController.updateBubble(mRow.getEntry()); + verify(mNotificationEntryManager, times(1)).updateNotifications(); mBubbleController.updateBubble(mRow2.getEntry()); + verify(mNotificationEntryManager, times(2)).updateNotifications(); assertTrue(mBubbleController.hasBubbles()); mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); assertFalse(mStatusBarWindowController.getBubblesShowing()); - verify(mNotificationEntryManager).updateNotifications(); + verify(mNotificationEntryManager, times(3)).updateNotifications(); assertTrue(mRow.getEntry().isBubbleDismissed()); assertTrue(mRow2.getEntry().isBubbleDismissed()); } @@ -215,7 +217,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand the stack BubbleStackView stackView = mBubbleController.getStackView(); - stackView.expandStack(); + mBubbleController.expandStack(); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key); assertTrue(mStatusBarWindowController.getBubbleExpanded()); @@ -224,7 +226,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertFalse(mRow.getEntry().showInShadeWhenBubble()); // Collapse - stackView.collapseStack(); + mBubbleController.collapseStack(); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key); assertFalse(mBubbleController.isStackExpanded()); assertFalse(mStatusBarWindowController.getBubbleExpanded()); @@ -245,23 +247,24 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - stackView.expandStack(); + mBubbleController.expandStack(); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key); - - // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); - assertFalse(mRow2.getEntry().showInShadeWhenBubble()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key); - // Switch which bubble is expanded - stackView.setExpandedBubble(mRow.getEntry()); + // First added is the one that is expanded assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry()); assertFalse(mRow.getEntry().showInShadeWhenBubble()); + // Switch which bubble is expanded + mBubbleController.selectBubble(mRow2.getEntry().key); + stackView.setExpandedBubble(mRow2.getEntry()); + assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); + assertFalse(mRow2.getEntry().showInShadeWhenBubble()); + // collapse for previous bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key); // expand for selected bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key); // Collapse mBubbleController.collapseStack(); @@ -280,7 +283,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - stackView.expandStack(); + mBubbleController.expandStack(); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key); @@ -299,30 +302,30 @@ public class BubbleControllerTest extends SysuiTestCase { // Expand BubbleStackView stackView = mBubbleController.getStackView(); - stackView.expandStack(); + mBubbleController.expandStack(); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key); - // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); - assertFalse(mRow2.getEntry().showInShadeWhenBubble()); + // First added is the one that is expanded + assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry()); + assertFalse(mRow.getEntry().showInShadeWhenBubble()); // Dismiss currently expanded mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(), BubbleController.DISMISS_USER_GESTURE); - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key); // Make sure next bubble is selected - assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key); + assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key); // Dismiss that one mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key); verify(mBubbleStateChangeListener).onHasBubblesChanged(false); assertFalse(mBubbleController.hasBubbles()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java index 801308fc77da..da31134d13b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java @@ -48,7 +48,7 @@ public class BubbleStackViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mStackView = new BubbleStackView(mContext, new BubbleData(), null); + mStackView = new BubbleStackView(mContext, new BubbleData(getContext()), null); mBubble.entry = mNotifEntry; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 2172a125ba85..3662c3860177 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -98,7 +98,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mViewHierarchyManager = new NotificationViewHierarchyManager(mContext, mLockscreenUserManager, mGroupManager, mVisualStabilityManager, mock(StatusBarStateControllerImpl.class), mEntryManager, - () -> mShadeController, new BubbleData()); + () -> mShadeController, new BubbleData(mContext)); Dependency.get(InitController.class).executePostInitTasks(); mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer); } |