Merge "Removing groups & ongoing from bubble ordering" into rvc-dev

This commit is contained in:
Mady Mellor
2020-05-27 00:06:33 +00:00
committed by Android (Google) Code Review
5 changed files with 115 additions and 432 deletions

View File

@@ -57,7 +57,6 @@ class Bubble implements BubbleViewProvider {
private NotificationEntry mEntry;
private final String mKey;
private final String mGroupId;
private long mLastUpdated;
private long mLastAccessed;
@@ -96,17 +95,10 @@ class Bubble implements BubbleViewProvider {
private int mDotColor;
private Path mDotPath;
public static String groupId(NotificationEntry entry) {
UserHandle user = entry.getSbn().getUser();
return user.getIdentifier() + "|" + entry.getSbn().getPackageName();
}
// TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble
Bubble(ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
mKey = shortcutInfo.getId();
mGroupId = shortcutInfo.getId();
}
/** Used in tests when no UI is required. */
@@ -116,7 +108,6 @@ class Bubble implements BubbleViewProvider {
mEntry = e;
mKey = e.getKey();
mLastUpdated = e.getSbn().getPostTime();
mGroupId = groupId(e);
mSuppressionListener = listener;
}
@@ -129,10 +120,6 @@ class Bubble implements BubbleViewProvider {
return mEntry;
}
public String getGroupId() {
return mGroupId;
}
public String getPackageName() {
return mEntry.getSbn().getPackageName();
}
@@ -297,19 +284,12 @@ class Bubble implements BubbleViewProvider {
}
/**
* @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
* @return the last time this bubble was updated or accessed, whichever is most recent.
*/
long getLastActivity() {
return Math.max(mLastUpdated, mLastAccessed);
}
/**
* @return the timestamp in milliseconds of the most recent notification entry for this bubble
*/
long getLastUpdateTime() {
return mLastUpdated;
}
/**
* @return if the bubble was ever expanded
*/
@@ -411,15 +391,6 @@ class Bubble implements BubbleViewProvider {
return mFlyoutMessage;
}
/**
* Returns whether the notification for this bubble is a foreground service. It shows that this
* is an ongoing bubble.
*/
boolean isOngoing() {
int flags = mEntry.getSbn().getNotification().flags;
return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
}
float getDesiredHeight(Context context) {
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
boolean useRes = data.getDesiredHeightResId() != 0;
@@ -562,7 +533,7 @@ class Bubble implements BubbleViewProvider {
normalX,
normalY,
this.showInShade(),
this.isOngoing(),
false /* isOngoing (unused) */,
false /* isAppForeground (unused) */);
}
}

View File

@@ -21,8 +21,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static java.util.stream.Collectors.toList;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
@@ -45,7 +43,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
@@ -62,9 +59,6 @@ public class BubbleData {
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_MAX_SORT_KEY_DESCENDING =
Comparator.<Map.Entry<String, Long>, Long>comparing(Map.Entry::getValue).reversed();
/** Contains information about changes that have been made to the state of bubbles. */
static final class Update {
boolean expandedChanged;
@@ -295,8 +289,6 @@ public class BubbleData {
if (!mExpanded) {
setExpandedInternal(true);
}
} else if (mSelectedBubble == null) {
setSelectedBubbleInternal(bubble);
}
boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
@@ -370,20 +362,11 @@ public class BubbleData {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doAdd: " + bubble);
}
int minInsertPoint = 0;
boolean newGroup = !hasBubbleWithGroupId(bubble.getGroupId());
if (isExpanded()) {
// first bubble of a group goes to the beginning, otherwise within the existing group
minInsertPoint = newGroup ? 0 : findFirstIndexForGroup(bubble.getGroupId());
}
if (insertBubble(minInsertPoint, bubble) < mBubbles.size() - 1) {
mStateChange.orderChanged = true;
}
mBubbles.add(0, bubble);
mStateChange.addedBubble = bubble;
// Adding the first bubble doesn't change the order
mStateChange.orderChanged = mBubbles.size() > 1;
if (!isExpanded()) {
mStateChange.orderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId()));
// Top bubble becomes selected.
setSelectedBubbleInternal(mBubbles.get(0));
}
}
@@ -406,14 +389,10 @@ public class BubbleData {
}
mStateChange.updatedBubble = bubble;
if (!isExpanded()) {
// while collapsed, update causes re-pack
int prevPos = mBubbles.indexOf(bubble);
mBubbles.remove(bubble);
int newPos = insertBubble(0, bubble);
if (prevPos != newPos) {
packGroup(newPos);
mStateChange.orderChanged = true;
}
mBubbles.add(0, bubble);
mStateChange.orderChanged = prevPos != 0;
setSelectedBubbleInternal(mBubbles.get(0));
}
}
@@ -581,7 +560,6 @@ public class BubbleData {
Log.e(TAG, "Attempt to expand stack without selected bubble!");
return;
}
mSelectedBubble.markUpdatedAt(mTimeSource.currentTimeMillis());
mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
mStateChange.orderChanged |= repackAll();
} else if (!mBubbles.isEmpty()) {
@@ -596,17 +574,11 @@ public class BubbleData {
}
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.
int index = mBubbles.indexOf(mSelectedBubble);
if (index != 0) {
mBubbles.remove(mSelectedBubble);
mBubbles.add(0, mSelectedBubble);
mStateChange.orderChanged |= packGroup(0);
mStateChange.orderChanged = true;
}
}
}
@@ -616,91 +588,12 @@ public class BubbleData {
}
private static long sortKey(Bubble bubble) {
long key = bubble.getLastUpdateTime();
if (bubble.isOngoing()) {
// Set 2nd highest bit (signed long int), to partition between ongoing and regular
key |= 0x4000000000000000L;
}
return key;
return bubble.getLastActivity();
}
/**
* Locates and inserts the bubble into a sorted position. The is inserted
* based on sort key, groupId is not considered. A call to {@link #packGroup(int)} may be
* required to keep grouping intact.
*
* @param minPosition the first insert point to consider
* @param newBubble the bubble to insert
* @return the position where the bubble was inserted
*/
private int insertBubble(int minPosition, Bubble newBubble) {
long newBubbleSortKey = sortKey(newBubble);
String previousGroupId = null;
for (int pos = minPosition; pos < mBubbles.size(); pos++) {
Bubble bubbleAtPos = mBubbles.get(pos);
String groupIdAtPos = bubbleAtPos.getGroupId();
boolean atStartOfGroup = !groupIdAtPos.equals(previousGroupId);
if (atStartOfGroup && newBubbleSortKey > sortKey(bubbleAtPos)) {
// Insert before the start of first group which has older bubbles.
mBubbles.add(pos, newBubble);
return pos;
}
previousGroupId = groupIdAtPos;
}
mBubbles.add(newBubble);
return mBubbles.size() - 1;
}
private boolean hasBubbleWithGroupId(String groupId) {
return mBubbles.stream().anyMatch(b -> b.getGroupId().equals(groupId));
}
private int findFirstIndexForGroup(String appId) {
for (int i = 0; i < mBubbles.size(); i++) {
Bubble bubbleAtPos = mBubbles.get(i);
if (bubbleAtPos.getGroupId().equals(appId)) {
return i;
}
}
return 0;
}
/**
* Starting at the given position, moves all bubbles with the same group id to follow. Bubbles
* at positions lower than {@code position} are unchanged. Relative order within the group
* 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 boolean packGroup(int position) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "packGroup: position=" + position);
}
Bubble groupStart = mBubbles.get(position);
final String groupAppId = groupStart.getGroupId();
List<Bubble> moving = new ArrayList<>();
// Walk backward, collect bubbles within the group
for (int i = mBubbles.size() - 1; i > position; i--) {
if (mBubbles.get(i).getGroupId().equals(groupAppId)) {
moving.add(0, mBubbles.get(i));
}
}
if (moving.isEmpty()) {
return false;
}
mBubbles.removeAll(moving);
mBubbles.addAll(position + 1, moving);
return true;
}
/**
* 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 its bubbles. Bubbles
* within each group are then sorted by lastUpdated descending.
* This applies a full sort and group pass to all existing bubbles.
* Bubbles are sorted by lastUpdated descending.
*
* @return true if the position of any bubbles changed as a result
*/
@@ -711,31 +604,11 @@ public class BubbleData {
if (mBubbles.isEmpty()) {
return false;
}
Map<String, Long> groupLastActivity = new HashMap<>();
for (Bubble bubble : mBubbles) {
long maxSortKeyForGroup = groupLastActivity.getOrDefault(bubble.getGroupId(), 0L);
long sortKeyForBubble = sortKey(bubble);
if (sortKeyForBubble > maxSortKeyForGroup) {
groupLastActivity.put(bubble.getGroupId(), sortKeyForBubble);
}
}
// Sort groups by their most recently active bubble
List<String> groupsByMostRecentActivity =
groupLastActivity.entrySet().stream()
.sorted(GROUPS_BY_MAX_SORT_KEY_DESCENDING)
.map(Map.Entry::getKey)
.collect(toList());
List<Bubble> repacked = new ArrayList<>(mBubbles.size());
// For each group, add bubbles, freshest to oldest
for (String appId : groupsByMostRecentActivity) {
mBubbles.stream()
.filter((b) -> b.getGroupId().equals(appId))
.sorted(BUBBLES_BY_SORT_KEY_DESCENDING)
.forEachOrdered(repacked::add);
}
// Add bubbles, freshest to oldest
mBubbles.stream()
.sorted(BUBBLES_BY_SORT_KEY_DESCENDING)
.forEachOrdered(repacked::add);
if (repacked.equals(mBubbles)) {
return false;
}
@@ -778,11 +651,12 @@ public class BubbleData {
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
/**
* The set of bubbles in overflow.
*/
@VisibleForTesting(visibility = PRIVATE)
public List<Bubble> getOverflowBubbles() {
List<Bubble> getOverflowBubbles() {
return Collections.unmodifiableList(mOverflowBubbles);
}

View File

@@ -69,10 +69,10 @@ public class BubbleDebugConfig {
&& selected.getKey() != BubbleOverflow.KEY
&& bubble == selected);
String arrow = isSelected ? "=>" : " ";
sb.append(String.format("%s Bubble{act=%12d, ongoing=%d, key=%s}\n",
sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}\n",
arrow,
bubble.getLastActivity(),
(bubble.isOngoing() ? 1 : 0),
(bubble.showInShade() ? 1 : 0),
bubble.getKey()));
}
}

View File

@@ -49,7 +49,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -188,7 +187,6 @@ public class BubbleStackView extends FrameLayout
private final SpringAnimation mExpandedViewYAnim;
private final BubbleData mBubbleData;
private final Vibrator mVibrator;
private final ValueAnimator mDesaturateAndDarkenAnimator;
private final Paint mDesaturateAndDarkenPaint = new Paint();
@@ -701,8 +699,6 @@ public class BubbleStackView extends FrameLayout
// We use the real size & subtract screen decorations / window insets ourselves when needed
wm.getDefaultDisplay().getRealSize(mDisplaySize);
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
@@ -2334,7 +2330,7 @@ public class BubbleStackView extends FrameLayout
getNormalizedXPosition(),
getNormalizedYPosition(),
bubble.showInShade(),
bubble.isOngoing(),
false /* isOngoing (unused) */,
false /* isAppForeground (unused) */);
}
}

View File

@@ -20,11 +20,8 @@ import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanki
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -258,14 +255,15 @@ public class BubbleDataTest extends SysuiTestCase {
assertThat(update.updatedBubble.showFlyout()).isFalse();
}
// COLLAPSED / ADD
//
// Overflow
//
/**
* Verifies that the number of bubbles is not allowed to exceed the maximum. The limit is
* enforced by expiring the bubble which was least recently updated (lowest timestamp).
* Verifies that when the bubble stack reaches its maximum, the oldest bubble is overflowed.
*/
@Test
public void test_collapsed_addBubble_atMaxBubbles_overflowsOldest() {
public void testOverflow_add_stackAtMaxBubbles_overflowsOldest() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -288,8 +286,12 @@ public class BubbleDataTest extends SysuiTestCase {
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
}
/**
* Verifies that once the number of overflowed bubbles reaches its maximum, the oldest
* overflow bubble is removed.
*/
@Test
public void testOverflowBubble_maxReached_bubbleRemoved() {
public void testOverflow_maxReached_bubbleRemoved() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -308,16 +310,41 @@ public class BubbleDataTest extends SysuiTestCase {
}
/**
* Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
* <p>
* Placement within the list is based on lastUpdate (post time of the notification), descending
* order (with most recent first).
*
* @see #test_expanded_addBubble_sortAndGrouping_newGroup()
* @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
* Verifies that overflow bubbles are canceled on notif entry removal.
*/
@Test
public void test_collapsed_addBubble_sortAndGrouping() {
public void testOverflow_notifCanceled_removesOverflowBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryA3, 3000);
sendUpdatedEntryAtTime(mEntryB1, 4000);
sendUpdatedEntryAtTime(mEntryB2, 5000);
sendUpdatedEntryAtTime(mEntryB3, 6000); // [A2, A3, B1, B2, B3], overflow: [A1]
sendUpdatedEntryAtTime(mEntryC1, 7000); // [A3, B1, B2, B3, C1], overflow: [A2, A1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
// Test
mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of());
}
// COLLAPSED / ADD
/**
* Verifies that new bubbles insert to the left when collapsed.
* <p>
* Placement within the list is based on {@link Bubble#getLastActivity()}, descending
* order (with most recent first).
*/
@Test
public void test_collapsed_addBubble() {
// Setup
mBubbleData.setListener(mListener);
@@ -336,41 +363,7 @@ public class BubbleDataTest extends SysuiTestCase {
sendUpdatedEntryAtTime(mEntryA2, 4000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1);
}
/**
* Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
* Additionally, any bubble which is ongoing is considered "newer" than any non-ongoing bubble.
* <p>
* Because of the ongoing bubble, the new bubble cannot be placed in the first position. This
* causes the 'B' group to remain last, despite having a new button added.
*
* @see #test_expanded_addBubble_sortAndGrouping_newGroup()
* @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
*/
@Test
public void test_collapsed_addBubble_sortAndGrouping_withOngoing() {
// Setup
mBubbleData.setListener(mListener);
// Test
setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
verifyUpdateReceived();
assertOrderNotChanged();
sendUpdatedEntryAtTime(mEntryB1, 2000);
verifyUpdateReceived();
assertOrderNotChanged();
sendUpdatedEntryAtTime(mEntryB2, 3000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA1, mBubbleB2, mBubbleB1);
sendUpdatedEntryAtTime(mEntryA2, 4000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1);
assertOrderChangedTo(mBubbleA2, mBubbleB2, mBubbleB1, mBubbleA1);
}
/**
@@ -378,7 +371,6 @@ public class BubbleDataTest extends SysuiTestCase {
* the collapsed state.
*
* @see #test_collapsed_updateBubble_selectionChanges()
* @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
*/
@Test
public void test_collapsed_addBubble_selectionChanges() {
@@ -403,58 +395,27 @@ public class BubbleDataTest extends SysuiTestCase {
assertSelectionChangedTo(mBubbleA2);
}
/**
* Verifies that while collapsed, the selection will not change if the selected bubble is
* ongoing. It remains the top bubble and as such remains selected.
*
* @see #test_collapsed_addBubble_selectionChanges()
*/
@Test
public void test_collapsed_addBubble_noSelectionChanges_withOngoing() {
// Setup
setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
mBubbleData.setListener(mListener);
// Test
sendUpdatedEntryAtTime(mEntryB1, 2000);
verifyUpdateReceived();
assertSelectionNotChanged();
sendUpdatedEntryAtTime(mEntryB2, 3000);
verifyUpdateReceived();
assertSelectionNotChanged();
sendUpdatedEntryAtTime(mEntryA2, 4000);
verifyUpdateReceived();
assertSelectionNotChanged();
assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1); // selection unchanged
}
// COLLAPSED / REMOVE
/**
* Verifies that groups may reorder when bubbles are removed, while the stack is in the
* collapsed state.
* Verifies order of bubbles after a removal.
*/
@Test
public void test_collapsed_removeBubble_sortAndGrouping() {
public void test_collapsed_removeBubble_sort() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
// TODO: this should fail if things work as I expect them to?
assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
}
/**
* Verifies that onOrderChanged is not called when a bubble is removed if the removal does not
* cause other bubbles to change position.
@@ -465,61 +426,15 @@ public class BubbleDataTest extends SysuiTestCase {
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderNotChanged();
}
/**
* Verifies that bubble ordering reverts to normal when an ongoing bubble is removed. A group
* which has a newer bubble may move to the front after the ongoing bubble is removed.
*/
@Test
public void test_collapsed_removeBubble_sortAndGrouping_withOngoing() {
// Setup
setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryB1, 3000);
sendUpdatedEntryAtTime(mEntryB2, 4000); // [A1*, A2, B2, B1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2);
}
/**
* Verifies that overflow bubbles are canceled on notif entry removal.
*/
@Test
public void test_removeOverflowBubble_forCanceledNotif() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryA3, 3000);
sendUpdatedEntryAtTime(mEntryB1, 4000);
sendUpdatedEntryAtTime(mEntryB2, 5000);
sendUpdatedEntryAtTime(mEntryB3, 6000); // [A2, A3, B1, B2, B3], overflow: [A1]
sendUpdatedEntryAtTime(mEntryC1, 7000); // [A3, B1, B2, B3, C1], overflow: [A2, A1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
// Test
mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of());
}
/**
* Verifies that when the selected bubble is removed with the stack in the collapsed state,
* the selection moves to the next most-recently updated bubble.
@@ -530,7 +445,7 @@ public class BubbleDataTest extends SysuiTestCase {
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
@@ -542,26 +457,26 @@ public class BubbleDataTest extends SysuiTestCase {
// COLLAPSED / UPDATE
/**
* Verifies that bubble and group ordering may change with updates while the stack is in the
* Verifies that bubble ordering changes with updates while the stack is in the
* collapsed state.
*/
@Test
public void test_collapsed_updateBubble_orderAndGrouping() {
public void test_collapsed_updateBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
sendUpdatedEntryAtTime(mEntryB1, 5000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleB2, mBubbleA1);
sendUpdatedEntryAtTime(mEntryA1, 6000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2);
assertOrderChangedTo(mBubbleA1, mBubbleB1, mBubbleA2, mBubbleB2);
}
/**
@@ -573,7 +488,7 @@ public class BubbleDataTest extends SysuiTestCase {
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
@@ -586,26 +501,6 @@ public class BubbleDataTest extends SysuiTestCase {
assertSelectionChangedTo(mBubbleA1);
}
/**
* Verifies that selection does not change in response to updates when collapsed, if the
* selected bubble is ongoing.
*/
@Test
public void test_collapsed_updateBubble_noSelectionChanges_withOngoing() {
// Setup
setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
sendUpdatedEntryAtTime(mEntryA2, 4000); // [A1*, A2, B2, B1]
mBubbleData.setListener(mListener);
// Test
sendUpdatedEntryAtTime(mEntryB2, 5000); // [A1*, A2, B2, B1]
verifyUpdateReceived();
assertSelectionNotChanged();
}
/**
* Verifies that a request to expand the stack has no effect if there are no bubbles.
*/
@@ -618,6 +513,9 @@ public class BubbleDataTest extends SysuiTestCase {
verifyZeroInteractions(mListener);
}
/**
* Verifies that removing the last bubble clears the selected bubble and collapses the stack.
*/
@Test
public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
// Setup
@@ -629,23 +527,22 @@ public class BubbleDataTest extends SysuiTestCase {
// Verify the selection was cleared.
verifyUpdateReceived();
assertThat(mBubbleData.isExpanded()).isFalse();
assertSelectionCleared();
}
// EXPANDED / ADD
// EXPANDED / ADD / UPDATE
/**
* Verifies that bubbles added as part of a new group insert before existing groups while
* expanded.
* Verifies that bubbles are added at the front of the stack.
* <p>
* Placement within the list is based on lastUpdate (post time of the notification), descending
* Placement within the list is based on {@link Bubble#getLastActivity()}, descending
* order (with most recent first).
*
* @see #test_collapsed_addBubble_sortAndGrouping()
* @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
* @see #test_collapsed_addBubble()
*/
@Test
public void test_expanded_addBubble_sortAndGrouping_newGroup() {
public void test_expanded_addBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -656,65 +553,15 @@ public class BubbleDataTest extends SysuiTestCase {
// Test
sendUpdatedEntryAtTime(mEntryC1, 4000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB1, mBubbleC1, mBubbleA2, mBubbleA1);
assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1);
}
/**
* Verifies that bubbles added as part of a new group insert before existing groups while
* expanded, but not before any groups with ongoing bubbles.
*
* @see #test_collapsed_addBubble_sortAndGrouping_withOngoing()
* @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
*/
@Test
public void test_expanded_addBubble_sortAndGrouping_newGroup_withOngoing() {
// Setup
setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryB1, 3000); // [A1*, A2, B1]
changeExpandedStateAtTime(true, 4000L);
mBubbleData.setListener(mListener);
// Test
sendUpdatedEntryAtTime(mEntryC1, 4000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleC1, mBubbleB1);
}
/**
* Verifies that bubbles added as part of an existing group insert to the beginning of that
* group. The order of groups within the list must not change while in the expanded state.
*
* @see #test_collapsed_addBubble_sortAndGrouping()
* @see #test_expanded_addBubble_sortAndGrouping_newGroup()
*/
@Test
public void test_expanded_addBubble_sortAndGrouping_existingGroup() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
changeExpandedStateAtTime(true, 4000L);
mBubbleData.setListener(mListener);
// Test
sendUpdatedEntryAtTime(mEntryA3, 4000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1);
}
// EXPANDED / UPDATE
/**
* Verifies that updates to bubbles while expanded do not result in any change to sorting
* or grouping of bubbles or sorting of groups.
*
* @see #test_collapsed_addBubble_sortAndGrouping()
* @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
* of bubbles.
*/
@Test
public void test_expanded_updateBubble_sortAndGrouping_noChanges() {
public void test_expanded_updateBubble_noChanges() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -733,7 +580,6 @@ public class BubbleDataTest extends SysuiTestCase {
* Verifies that updates to bubbles while expanded do not result in any change to selection.
*
* @see #test_collapsed_addBubble_selectionChanges()
* @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
*/
@Test
public void test_expanded_updateBubble_noSelectionChanges() {
@@ -762,26 +608,24 @@ public class BubbleDataTest extends SysuiTestCase {
// EXPANDED / REMOVE
/**
* Verifies that removing a bubble while expanded does not result in reordering of groups
* or any of the remaining bubbles.
* Verifies that removing a bubble while expanded does not result in reordering of bubbles.
*
* @see #test_collapsed_addBubble_sortAndGrouping()
* @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
* @see #test_collapsed_addBubble()
*/
@Test
public void test_expanded_removeBubble_sortAndGrouping() {
public void test_expanded_removeBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryA2, 3000);
sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, A2, B1, A1]
changeExpandedStateAtTime(true, 5000L);
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1);
assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleA1);
}
/**
@@ -789,8 +633,7 @@ public class BubbleDataTest extends SysuiTestCase {
* selected. The replacement selection is the bubble which appears at the same index as the
* previous one, or the previous index if this was the last position.
*
* @see #test_collapsed_addBubble_sortAndGrouping()
* @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
* @see #test_collapsed_addBubble()
*/
@Test
public void test_expanded_removeBubble_selectionChanges_whenSelectedRemoved() {
@@ -800,17 +643,17 @@ public class BubbleDataTest extends SysuiTestCase {
sendUpdatedEntryAtTime(mEntryA2, 3000);
sendUpdatedEntryAtTime(mEntryB2, 4000);
changeExpandedStateAtTime(true, 5000L);
mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
mBubbleData.setSelectedBubble(mBubbleA2); // [B2, A2^, B1, A1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleA1);
mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleB1);
mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleA1);
}
@Test
@@ -838,7 +681,6 @@ public class BubbleDataTest extends SysuiTestCase {
* expanded.
* <p>
* When the stack transitions to the collapsed state, the selected bubble is brought to the top.
* Bubbles within the same group should move up with it.
* <p>
* When the stack transitions back to the expanded state, this new order is kept as is.
*/
@@ -849,13 +691,13 @@ public class BubbleDataTest extends SysuiTestCase {
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryA2, 3000);
sendUpdatedEntryAtTime(mEntryB2, 4000);
changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=6000*, A2=3000, A1=1000]
changeExpandedStateAtTime(true, 5000L); // [B2=4000, A2=3000, B1=2000, A1=1000]
sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, A2=3000, B1=6000, A1=1000]
setCurrentTime(7000);
mBubbleData.setSelectedBubble(mBubbleA2);
mBubbleData.setListener(mListener);
assertThat(mBubbleData.getBubbles()).isEqualTo(
ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
ImmutableList.of(mBubbleB2, mBubbleA2, mBubbleB1, mBubbleA1));
// Test
@@ -863,18 +705,17 @@ public class BubbleDataTest extends SysuiTestCase {
// stack is expanded. When next collapsed, sorting will be applied and saved, just prior
// to moving the selected bubble to the top (first).
//
// In this case, the expected re-expand state will be: [A2, A1, B1, B2]
// In this case, the expected re-expand state will be: [A2^, B1, B2, A1]
//
// collapse -> selected bubble (A2) moves first.
changeExpandedStateAtTime(false, 8000L);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleB2, mBubbleA1);
}
/**
* When a change occurs while collapsed (any update, add, remove), the previous expanded
* order and grouping becomes invalidated, and the order and grouping when next expanded will
* remain the same as collapsed.
* order becomes invalidated, the stack is resorted and will reflect that when next expanded.
*/
@Test
public void test_expansionChanges_withUpdatesWhileCollapsed() {
@@ -883,10 +724,10 @@ public class BubbleDataTest extends SysuiTestCase {
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryA2, 3000);
sendUpdatedEntryAtTime(mEntryB2, 4000);
changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=*6000, A2=3000, A1=1000]
changeExpandedStateAtTime(true, 5000L); // [B2=4000, A2=3000, B1=2000, A1=1000]
sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, A2=3000, B1=6000, A1=1000]
setCurrentTime(7000);
mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
mBubbleData.setSelectedBubble(mBubbleA2); // [B2, A2^, B1, A1]
mBubbleData.setListener(mListener);
// Test
@@ -895,7 +736,7 @@ public class BubbleDataTest extends SysuiTestCase {
// stack is expanded. When next collapsed, sorting will be applied and saved, just prior
// to moving the selected bubble to the top (first).
//
// In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
// In this case, the expected re-expand state will be: [A2^, B1, B2, A1]
//
// That state is restored as long as no changes occur (add/remove/update) while in
// the collapsed state.
@@ -903,11 +744,12 @@ public class BubbleDataTest extends SysuiTestCase {
// collapse -> selected bubble (A2) moves first.
changeExpandedStateAtTime(false, 8000L);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleB2, mBubbleA1);
// An update occurs, which causes sorting, and this invalidates the previously saved order.
sendUpdatedEntryAtTime(mEntryA2, 9000);
sendUpdatedEntryAtTime(mEntryA1, 9000);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2);
// No order changes when expanding because the new sorted order remains.
changeExpandedStateAtTime(true, 10000L);