Merge "Updates BubbleData sorting and grouping to spec" into qt-dev am: d6cddfedf9
am: 5a3826bead
Change-Id: If58709393408c875253364860582316fdf133ea3
This commit is contained in:
@@ -46,7 +46,7 @@ class Bubble {
|
||||
private long mLastUpdated;
|
||||
private long mLastAccessed;
|
||||
|
||||
private static String groupId(NotificationEntry entry) {
|
||||
public static String groupId(NotificationEntry entry) {
|
||||
UserHandle user = entry.notification.getUser();
|
||||
return user.getIdentifier() + "|" + entry.notification.getPackageName();
|
||||
}
|
||||
@@ -120,10 +120,27 @@ class Bubble {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
|
||||
*/
|
||||
public long getLastActivity() {
|
||||
return Math.max(mLastUpdated, mLastAccessed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timestamp in milliseconds of the most recent notification entry for this bubble
|
||||
*/
|
||||
public long getLastUpdateTime() {
|
||||
return mLastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timestamp in milliseconds when this bubble was last displayed in expanded state
|
||||
*/
|
||||
public long getLastAccessTime() {
|
||||
return mLastAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be invoked whenever a Bubble is accessed (selected while expanded).
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,9 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static com.android.systemui.statusbar.StatusBarState.SHADE;
|
||||
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
@@ -73,6 +76,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowController;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -88,11 +92,12 @@ import javax.inject.Singleton;
|
||||
public class BubbleController implements ConfigurationController.ConfigurationListener {
|
||||
|
||||
private static final String TAG = "BubbleController";
|
||||
private static final boolean DEBUG = true;
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
|
||||
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
|
||||
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
|
||||
@interface DismissReason {}
|
||||
|
||||
static final int DISMISS_USER_GESTURE = 1;
|
||||
@@ -510,6 +515,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
|
||||
@Override
|
||||
public void onOrderChanged(List<Bubble> bubbles) {
|
||||
if (mStackView != null) {
|
||||
mStackView.updateBubbleOrder(bubbles);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -526,13 +534,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFlyoutText(Bubble bubble, String text) {
|
||||
if (mStackView != null) {
|
||||
mStackView.animateInFlyoutForBubble(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
mNotificationEntryManager.updateNotifications();
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -53,10 +54,10 @@ public class BubbleData {
|
||||
|
||||
private static final int MAX_BUBBLES = 5;
|
||||
|
||||
private static final Comparator<Bubble> BUBBLES_BY_LAST_ACTIVITY_DESCENDING =
|
||||
Comparator.comparing(Bubble::getLastActivity).reversed();
|
||||
private static final Comparator<Bubble> BUBBLES_BY_SORT_KEY_DESCENDING =
|
||||
Comparator.comparing(BubbleData::sortKey).reversed();
|
||||
|
||||
private static final Comparator<Map.Entry<String, Long>> GROUPS_BY_LAST_ACTIVITY_DESCENDING =
|
||||
private static final Comparator<Map.Entry<String, Long>> GROUPS_BY_MAX_SORT_KEY_DESCENDING =
|
||||
Comparator.<Map.Entry<String, Long>, Long>comparing(Map.Entry::getValue).reversed();
|
||||
|
||||
/**
|
||||
@@ -105,9 +106,6 @@ public class BubbleData {
|
||||
*/
|
||||
void onExpandedChanged(boolean expanded);
|
||||
|
||||
/** Flyout text should animate in, showing the given text. */
|
||||
void showFlyoutText(Bubble bubble, String text);
|
||||
|
||||
/** Commit any pending operations (since last call of apply()) */
|
||||
void apply();
|
||||
}
|
||||
@@ -121,15 +119,19 @@ public class BubbleData {
|
||||
private Bubble mSelectedBubble;
|
||||
private boolean mExpanded;
|
||||
|
||||
// TODO: ensure this is invalidated at the appropriate time
|
||||
private int mSelectedBubbleExpandedPosition = -1;
|
||||
// State tracked during an operation -- keeps track of what listener events to dispatch.
|
||||
private boolean mExpandedChanged;
|
||||
private boolean mOrderChanged;
|
||||
private boolean mSelectionChanged;
|
||||
private Bubble mUpdatedBubble;
|
||||
private Bubble mAddedBubble;
|
||||
private final List<Pair<Bubble, Integer>> mRemovedBubbles = new ArrayList<>();
|
||||
|
||||
private TimeSource mTimeSource = System::currentTimeMillis;
|
||||
|
||||
@Nullable
|
||||
private Listener mListener;
|
||||
|
||||
@VisibleForTesting
|
||||
@Inject
|
||||
public BubbleData(Context context) {
|
||||
mContext = context;
|
||||
@@ -154,18 +156,19 @@ public class BubbleData {
|
||||
}
|
||||
|
||||
public void setExpanded(boolean expanded) {
|
||||
if (setExpandedInternal(expanded)) {
|
||||
dispatchApply();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setExpanded: " + expanded);
|
||||
}
|
||||
setExpandedInternal(expanded);
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
public void setSelectedBubble(Bubble bubble) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setSelectedBubble: " + bubble);
|
||||
}
|
||||
if (setSelectedBubbleInternal(bubble)) {
|
||||
dispatchApply();
|
||||
}
|
||||
setSelectedBubbleInternal(bubble);
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
public void notificationEntryUpdated(NotificationEntry entry) {
|
||||
@@ -177,12 +180,12 @@ public class BubbleData {
|
||||
// Create a new bubble
|
||||
bubble = new Bubble(entry, this::onBubbleBlocked);
|
||||
doAdd(bubble);
|
||||
dispatchOnBubbleAdded(bubble);
|
||||
trim();
|
||||
} else {
|
||||
// Updates an existing bubble
|
||||
bubble.setEntry(entry);
|
||||
doUpdate(bubble);
|
||||
dispatchOnBubbleUpdated(bubble);
|
||||
mUpdatedBubble = bubble;
|
||||
}
|
||||
if (shouldAutoExpand(entry)) {
|
||||
setSelectedBubbleInternal(bubble);
|
||||
@@ -192,7 +195,15 @@ public class BubbleData {
|
||||
} else if (mSelectedBubble == null) {
|
||||
setSelectedBubbleInternal(bubble);
|
||||
}
|
||||
dispatchApply();
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
|
||||
}
|
||||
doRemove(entry.key, reason);
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
private void doAdd(Bubble bubble) {
|
||||
@@ -202,14 +213,21 @@ public class BubbleData {
|
||||
int minInsertPoint = 0;
|
||||
boolean newGroup = !hasBubbleWithGroupId(bubble.getGroupId());
|
||||
if (isExpanded()) {
|
||||
// first bubble of a group goes to the end, otherwise it goes within the existing group
|
||||
minInsertPoint =
|
||||
newGroup ? mBubbles.size() : findFirstIndexForGroup(bubble.getGroupId());
|
||||
// first bubble of a group goes to the beginning, otherwise within the existing group
|
||||
minInsertPoint = newGroup ? 0 : findFirstIndexForGroup(bubble.getGroupId());
|
||||
}
|
||||
insertBubble(minInsertPoint, bubble);
|
||||
if (insertBubble(minInsertPoint, bubble) < mBubbles.size() - 1) {
|
||||
mOrderChanged = true;
|
||||
}
|
||||
mAddedBubble = bubble;
|
||||
if (!isExpanded()) {
|
||||
packGroup(findFirstIndexForGroup(bubble.getGroupId()));
|
||||
mOrderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId()));
|
||||
// Top bubble becomes selected.
|
||||
setSelectedBubbleInternal(mBubbles.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void trim() {
|
||||
if (mBubbles.size() > MAX_BUBBLES) {
|
||||
mBubbles.stream()
|
||||
// sort oldest first (ascending lastActivity)
|
||||
@@ -217,10 +235,7 @@ public class BubbleData {
|
||||
// skip the selected bubble
|
||||
.filter((b) -> !b.equals(mSelectedBubble))
|
||||
.findFirst()
|
||||
.ifPresent((b) -> {
|
||||
doRemove(b.getKey(), BubbleController.DISMISS_AGED);
|
||||
dispatchApply();
|
||||
});
|
||||
.ifPresent((b) -> doRemove(b.getKey(), BubbleController.DISMISS_AGED));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,43 +244,48 @@ public class BubbleData {
|
||||
Log.d(TAG, "doUpdate: " + bubble);
|
||||
}
|
||||
if (!isExpanded()) {
|
||||
// while collapsed, update causes re-sort
|
||||
// while collapsed, update causes re-pack
|
||||
int prevPos = mBubbles.indexOf(bubble);
|
||||
mBubbles.remove(bubble);
|
||||
insertBubble(0, bubble);
|
||||
packGroup(findFirstIndexForGroup(bubble.getGroupId()));
|
||||
int newPos = insertBubble(0, bubble);
|
||||
if (prevPos != newPos) {
|
||||
packGroup(newPos);
|
||||
mOrderChanged = true;
|
||||
}
|
||||
setSelectedBubbleInternal(mBubbles.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
|
||||
}
|
||||
doRemove(entry.key, reason);
|
||||
dispatchApply();
|
||||
}
|
||||
|
||||
private void doRemove(String key, @DismissReason int reason) {
|
||||
int indexToRemove = indexForKey(key);
|
||||
if (indexToRemove >= 0) {
|
||||
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
|
||||
if (mBubbles.size() == 1) {
|
||||
// Going to become empty, handle specially.
|
||||
setExpandedInternal(false);
|
||||
setSelectedBubbleInternal(null);
|
||||
}
|
||||
mBubbles.remove(indexToRemove);
|
||||
dispatchOnBubbleRemoved(bubbleToRemove, reason);
|
||||
|
||||
// Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
|
||||
if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
|
||||
// Move selection to the new bubble at the same position.
|
||||
int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
|
||||
Bubble newSelected = mBubbles.get(newIndex);
|
||||
setSelectedBubbleInternal(newSelected);
|
||||
}
|
||||
bubbleToRemove.setDismissed();
|
||||
maybeSendDeleteIntent(reason, bubbleToRemove.entry);
|
||||
if (indexToRemove == -1) {
|
||||
return;
|
||||
}
|
||||
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
|
||||
if (mBubbles.size() == 1) {
|
||||
// Going to become empty, handle specially.
|
||||
setExpandedInternal(false);
|
||||
setSelectedBubbleInternal(null);
|
||||
}
|
||||
if (indexToRemove < mBubbles.size() - 1) {
|
||||
// Removing anything but the last bubble means positions will change.
|
||||
mOrderChanged = true;
|
||||
}
|
||||
mBubbles.remove(indexToRemove);
|
||||
mRemovedBubbles.add(Pair.create(bubbleToRemove, reason));
|
||||
if (!isExpanded()) {
|
||||
mOrderChanged |= repackAll();
|
||||
}
|
||||
|
||||
// Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
|
||||
if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
|
||||
// Move selection to the new bubble at the same position.
|
||||
int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
|
||||
Bubble newSelected = mBubbles.get(newIndex);
|
||||
setSelectedBubbleInternal(newSelected);
|
||||
}
|
||||
bubbleToRemove.setDismissed();
|
||||
maybeSendDeleteIntent(reason, bubbleToRemove.entry);
|
||||
}
|
||||
|
||||
public void dismissAll(@DismissReason int reason) {
|
||||
@@ -281,87 +301,98 @@ public class BubbleData {
|
||||
Bubble bubble = mBubbles.remove(0);
|
||||
bubble.setDismissed();
|
||||
maybeSendDeleteIntent(reason, bubble.entry);
|
||||
dispatchOnBubbleRemoved(bubble, reason);
|
||||
mRemovedBubbles.add(Pair.create(bubble, reason));
|
||||
}
|
||||
dispatchApply();
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
private void dispatchApply() {
|
||||
if (mListener != null) {
|
||||
|
||||
private void dispatchPendingChanges() {
|
||||
if (mListener == null) {
|
||||
mExpandedChanged = false;
|
||||
mAddedBubble = null;
|
||||
mSelectionChanged = false;
|
||||
mRemovedBubbles.clear();
|
||||
mUpdatedBubble = null;
|
||||
mOrderChanged = false;
|
||||
return;
|
||||
}
|
||||
boolean anythingChanged = false;
|
||||
|
||||
if (mAddedBubble != null) {
|
||||
mListener.onBubbleAdded(mAddedBubble);
|
||||
mAddedBubble = null;
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
// Compat workaround: Always collapse first.
|
||||
if (mExpandedChanged && !mExpanded) {
|
||||
mListener.onExpandedChanged(mExpanded);
|
||||
mExpandedChanged = false;
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
if (mSelectionChanged) {
|
||||
mListener.onSelectionChanged(mSelectedBubble);
|
||||
mSelectionChanged = false;
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
if (!mRemovedBubbles.isEmpty()) {
|
||||
for (Pair<Bubble, Integer> removed : mRemovedBubbles) {
|
||||
mListener.onBubbleRemoved(removed.first, removed.second);
|
||||
}
|
||||
mRemovedBubbles.clear();
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
if (mUpdatedBubble != null) {
|
||||
mListener.onBubbleUpdated(mUpdatedBubble);
|
||||
mUpdatedBubble = null;
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
if (mOrderChanged) {
|
||||
mListener.onOrderChanged(mBubbles);
|
||||
mOrderChanged = false;
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
if (mExpandedChanged) {
|
||||
mListener.onExpandedChanged(mExpanded);
|
||||
mExpandedChanged = false;
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
if (anythingChanged) {
|
||||
mListener.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnBubbleAdded(Bubble bubble) {
|
||||
if (mListener != null) {
|
||||
mListener.onBubbleAdded(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnBubbleRemoved(Bubble bubble, @DismissReason int reason) {
|
||||
if (mListener != null) {
|
||||
mListener.onBubbleRemoved(bubble, reason);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnExpandedChanged(boolean expanded) {
|
||||
if (mListener != null) {
|
||||
mListener.onExpandedChanged(expanded);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnSelectionChanged(@Nullable Bubble bubble) {
|
||||
if (mListener != null) {
|
||||
mListener.onSelectionChanged(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnBubbleUpdated(Bubble bubble) {
|
||||
if (mListener != null) {
|
||||
mListener.onBubbleUpdated(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnOrderChanged(List<Bubble> bubbles) {
|
||||
if (mListener != null) {
|
||||
mListener.onOrderChanged(bubbles);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchShowFlyoutText(Bubble bubble, String text) {
|
||||
if (mListener != null) {
|
||||
mListener.showFlyoutText(bubble, text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(@Nullable Bubble bubble) {
|
||||
private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
|
||||
}
|
||||
if (Objects.equals(bubble, mSelectedBubble)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (bubble != null && !mBubbles.contains(bubble)) {
|
||||
Log.e(TAG, "Cannot select bubble which doesn't exist!"
|
||||
+ " (" + bubble + ") bubbles=" + mBubbles);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (mExpanded && bubble != null) {
|
||||
bubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
|
||||
}
|
||||
mSelectedBubble = bubble;
|
||||
dispatchOnSelectionChanged(mSelectedBubble);
|
||||
if (!mExpanded || mSelectedBubble == null) {
|
||||
mSelectedBubbleExpandedPosition = -1;
|
||||
}
|
||||
return true;
|
||||
mSelectionChanged = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,37 +400,53 @@ public class BubbleData {
|
||||
* the value changes.
|
||||
*
|
||||
* @param shouldExpand the new requested state
|
||||
* @return true if the state changed as a result
|
||||
*/
|
||||
private boolean setExpandedInternal(boolean shouldExpand) {
|
||||
private void setExpandedInternal(boolean shouldExpand) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
|
||||
}
|
||||
if (mExpanded == shouldExpand) {
|
||||
return false;
|
||||
}
|
||||
if (mSelectedBubble != null) {
|
||||
mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
|
||||
return;
|
||||
}
|
||||
if (shouldExpand) {
|
||||
if (mBubbles.isEmpty()) {
|
||||
Log.e(TAG, "Attempt to expand stack when empty!");
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (mSelectedBubble == null) {
|
||||
Log.e(TAG, "Attempt to expand stack without selected bubble!");
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
|
||||
mOrderChanged |= repackAll();
|
||||
} else if (!mBubbles.isEmpty()) {
|
||||
// Apply ordering and grouping rules from expanded -> collapsed, then save
|
||||
// the result.
|
||||
mOrderChanged |= repackAll();
|
||||
// Save the state which should be returned to when expanded (with no other changes)
|
||||
|
||||
if (mBubbles.indexOf(mSelectedBubble) > 0) {
|
||||
// Move the selected bubble to the top while collapsed.
|
||||
if (!mSelectedBubble.isOngoing() && mBubbles.get(0).isOngoing()) {
|
||||
// The selected bubble cannot be raised to the first position because
|
||||
// there is an ongoing bubble there. Instead, force the top ongoing bubble
|
||||
// to become selected.
|
||||
setSelectedBubbleInternal(mBubbles.get(0));
|
||||
} else {
|
||||
// Raise the selected bubble (and it's group) up to the front so the selected
|
||||
// bubble remains on top.
|
||||
mBubbles.remove(mSelectedBubble);
|
||||
mBubbles.add(0, mSelectedBubble);
|
||||
packGroup(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repackAll();
|
||||
}
|
||||
mExpanded = shouldExpand;
|
||||
dispatchOnExpandedChanged(mExpanded);
|
||||
return true;
|
||||
mExpandedChanged = true;
|
||||
}
|
||||
|
||||
private static long sortKey(Bubble bubble) {
|
||||
long key = bubble.getLastActivity();
|
||||
long key = bubble.getLastUpdateTime();
|
||||
if (bubble.isOngoing()) {
|
||||
// Set 2nd highest bit (signed long int), to partition between ongoing and regular
|
||||
key |= 0x4000000000000000L;
|
||||
@@ -456,8 +503,9 @@ public class BubbleData {
|
||||
* unchanged. Relative order of any other bubbles are also unchanged.
|
||||
*
|
||||
* @param position the position of the first bubble for the group
|
||||
* @return true if the position of any bubbles has changed as a result
|
||||
*/
|
||||
private void packGroup(int position) {
|
||||
private boolean packGroup(int position) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "packGroup: position=" + position);
|
||||
}
|
||||
@@ -471,16 +519,27 @@ public class BubbleData {
|
||||
moving.add(0, mBubbles.get(i));
|
||||
}
|
||||
}
|
||||
if (moving.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
mBubbles.removeAll(moving);
|
||||
mBubbles.addAll(position + 1, moving);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void repackAll() {
|
||||
/**
|
||||
* This applies a full sort and group pass to all existing bubbles. The bubbles are grouped
|
||||
* by groupId. Each group is then sorted by the max(lastUpdated) time of it's bubbles. Bubbles
|
||||
* within each group are then sorted by lastUpdated descending.
|
||||
*
|
||||
* @return true if the position of any bubbles changed as a result
|
||||
*/
|
||||
private boolean repackAll() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "repackAll()");
|
||||
}
|
||||
if (mBubbles.isEmpty()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
Map<String, Long> groupLastActivity = new HashMap<>();
|
||||
for (Bubble bubble : mBubbles) {
|
||||
@@ -494,7 +553,7 @@ public class BubbleData {
|
||||
// Sort groups by their most recently active bubble
|
||||
List<String> groupsByMostRecentActivity =
|
||||
groupLastActivity.entrySet().stream()
|
||||
.sorted(GROUPS_BY_LAST_ACTIVITY_DESCENDING)
|
||||
.sorted(GROUPS_BY_MAX_SORT_KEY_DESCENDING)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(toList());
|
||||
|
||||
@@ -504,10 +563,14 @@ public class BubbleData {
|
||||
for (String appId : groupsByMostRecentActivity) {
|
||||
mBubbles.stream()
|
||||
.filter((b) -> b.getGroupId().equals(appId))
|
||||
.sorted(BUBBLES_BY_LAST_ACTIVITY_DESCENDING)
|
||||
.sorted(BUBBLES_BY_SORT_KEY_DESCENDING)
|
||||
.forEachOrdered(repacked::add);
|
||||
}
|
||||
if (repacked.equals(mBubbles)) {
|
||||
return false;
|
||||
}
|
||||
mBubbles = repacked;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) {
|
||||
@@ -527,21 +590,25 @@ public class BubbleData {
|
||||
}
|
||||
|
||||
private void onBubbleBlocked(NotificationEntry entry) {
|
||||
boolean changed = false;
|
||||
final String blockedPackage = entry.notification.getPackageName();
|
||||
final String blockedGroupId = Bubble.groupId(entry);
|
||||
int selectedIndex = mBubbles.indexOf(mSelectedBubble);
|
||||
for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
|
||||
Bubble bubble = i.next();
|
||||
if (bubble.getPackageName().equals(blockedPackage)) {
|
||||
if (bubble.getGroupId().equals(blockedGroupId)) {
|
||||
mRemovedBubbles.add(Pair.create(bubble, BubbleController.DISMISS_BLOCKED));
|
||||
i.remove();
|
||||
// TODO: handle removal of selected bubble, and collapse safely if emptied (see
|
||||
// dismissAll)
|
||||
dispatchOnBubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
dispatchApply();
|
||||
if (mBubbles.isEmpty()) {
|
||||
setExpandedInternal(false);
|
||||
setSelectedBubbleInternal(null);
|
||||
} else if (!mBubbles.contains(mSelectedBubble)) {
|
||||
// choose a new one
|
||||
int newIndex = Math.min(selectedIndex, mBubbles.size() - 1);
|
||||
Bubble newSelected = mBubbles.get(newIndex);
|
||||
setSelectedBubbleInternal(newSelected);
|
||||
}
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
private int indexForKey(String key) {
|
||||
|
||||
@@ -549,6 +549,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
mBubbleContainer.addView(bubble.iconView, 0,
|
||||
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
||||
ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
|
||||
animateInFlyoutForBubble(bubble);
|
||||
requestUpdate();
|
||||
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
|
||||
}
|
||||
@@ -570,10 +571,19 @@ public class BubbleStackView extends FrameLayout {
|
||||
|
||||
// via BubbleData.Listener
|
||||
void updateBubble(Bubble bubble) {
|
||||
animateInFlyoutForBubble(bubble);
|
||||
requestUpdate();
|
||||
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
|
||||
}
|
||||
|
||||
public void updateBubbleOrder(List<Bubble> bubbles) {
|
||||
for (int i = 0; i < bubbles.size(); i++) {
|
||||
Bubble bubble = bubbles.get(i);
|
||||
mBubbleContainer.moveViewTo(bubble.iconView, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -296,22 +296,22 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
BubbleStackView stackView = mBubbleController.getStackView();
|
||||
mBubbleController.expandStack();
|
||||
assertTrue(mBubbleController.isStackExpanded());
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
|
||||
|
||||
// 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());
|
||||
// Last added is the one that is expanded
|
||||
assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
|
||||
assertFalse(mRow2.getEntry().showInShadeWhenBubble());
|
||||
|
||||
// Switch which bubble is expanded
|
||||
mBubbleController.selectBubble(mRow.getEntry().key);
|
||||
stackView.setExpandedBubble(mRow.getEntry());
|
||||
assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
|
||||
assertFalse(mRow.getEntry().showInShadeWhenBubble());
|
||||
|
||||
// collapse for previous bubble
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
|
||||
// expand for selected bubble
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
|
||||
|
||||
// Collapse
|
||||
mBubbleController.collapseStack();
|
||||
@@ -352,27 +352,27 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
mBubbleController.expandStack();
|
||||
|
||||
assertTrue(mBubbleController.isStackExpanded());
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
|
||||
|
||||
// First added is the one that is expanded
|
||||
assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
|
||||
assertFalse(mRow.getEntry().showInShadeWhenBubble());
|
||||
// Last added is the one that is expanded
|
||||
assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
|
||||
assertFalse(mRow2.getEntry().showInShadeWhenBubble());
|
||||
|
||||
// Dismiss currently expanded
|
||||
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
|
||||
BubbleController.DISMISS_USER_GESTURE);
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
|
||||
|
||||
// Make sure next bubble is selected
|
||||
assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
|
||||
// Make sure first bubble is selected
|
||||
assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.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, mRow2.getEntry().key);
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
|
||||
verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
|
||||
assertFalse(mBubbleController.hasBubbles());
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user