BubbleData [6/n]: Splice BubbleData into code path

BubbleData internal representation becomes a list in prep
for managing order and grouping internally. Changes are
passed through bubbleData and dispatched for UI updates
via Listener.

Bug: 123542488
Test: atest BubbleControllerTest
Change-Id: I380ca718bc896307b31e73030b4202762a6cf353
This commit is contained in:
Mark Renouf
2019-04-08 15:02:54 -04:00
parent 364d2515ea
commit 71a3af6cd5
8 changed files with 433 additions and 289 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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() {}
/**
* The set of bubbles.
*/
public Collection<Bubble> getBubbles() {
return mBubbles.values();
public BubbleData(Context context) {
mContext = context;
}
@Nullable
public Bubble getBubble(String key) {
return mBubbles.get(key);
public boolean hasBubbles() {
return !mBubbles.isEmpty();
}
public void addBubble(Bubble b) {
mBubbles.put(b.getKey(), b);
public boolean isExpanded() {
return mExpanded;
}
@Nullable
public Bubble removeBubble(String key) {
return mBubbles.remove(key);
public boolean hasBubbleWithKey(String key) {
return getBubbleWithKey(key) != null;
}
public void updateBubble(String key, NotificationEntry newEntry) {
Bubble oldBubble = mBubbles.get(key);
if (oldBubble != null) {
oldBubble.setEntry(newEntry);
public void setExpanded(boolean expanded) {
if (setExpandedInternal(expanded)) {
mListener.apply();
}
}
public void clear() {
mBubbles.clear();
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();
}
}
/**
* 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
*/
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;
}
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);
}
}
}
}
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();
}
}
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;
}
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);
}
@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());
}
}

View File

@@ -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();
}
}
@@ -406,16 +401,6 @@ public class BubbleStackView extends FrameLayout {
mExpandListener = listener;
}
/**
* 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.
*/
@@ -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);
@@ -517,131 +530,17 @@ public class BubbleStackView extends FrameLayout {
notifyExpansionChanged(mExpandedBubble.entry, mIsExpanded);
}
/**
* 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
*/
@@ -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);
}
/**

View File

@@ -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;

View File

@@ -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);
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());
// 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());
}

View File

@@ -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;
}

View File

@@ -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);
}