diff options
3 files changed, 278 insertions, 250 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 55d3a4c85256..4ec79a64b2a3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -56,6 +56,7 @@ import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.util.Log; +import android.util.Pair; import android.view.Display; import android.view.IPinnedStackController; import android.view.IPinnedStackListener; @@ -514,62 +515,66 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @Override - public void onBubbleAdded(Bubble bubble) { - ensureStackViewCreated(); - mStackView.addBubble(bubble); - } + public void applyUpdate(BubbleData.Update update) { + if (mStackView == null && update.addedBubble != null) { + // Lazy init stack view when the first bubble is added. + ensureStackViewCreated(); + } - @Override - public void onBubbleRemoved(Bubble bubble, @DismissReason int reason) { - if (mStackView != null) { - mStackView.removeBubble(bubble); + // If not yet initialized, ignore all other changes. + if (mStackView == null) { + return; } - if (!mBubbleData.hasBubbleWithKey(bubble.getKey()) - && !bubble.entry.showInShadeWhenBubble()) { - // The bubble is gone & the notification is gone, time to actually remove it - mNotificationEntryManager.performRemoveNotification(bubble.entry.notification, - UNDEFINED_DISMISS_REASON); - } else { - // The notification is still in the shade but we've removed the bubble so - // lets make sure NoMan knows it's not a bubble anymore - try { - mBarService.onNotificationBubbleChanged(bubble.getKey(), false /* isBubble */); - } catch (RemoteException e) { - // Bad things have happened + + if (update.addedBubble != null) { + mStackView.addBubble(update.addedBubble); + } + + // Collapsing? Do this first before remaining steps. + if (update.expandedChanged && !update.expanded) { + mStackView.setExpanded(false); + } + + // Do removals, if any. + for (Pair<Bubble, Integer> removed : update.removedBubbles) { + final Bubble bubble = removed.first; + @DismissReason final int reason = removed.second; + mStackView.removeBubble(bubble); + + if (!mBubbleData.hasBubbleWithKey(bubble.getKey()) + && !bubble.entry.showInShadeWhenBubble()) { + // The bubble is gone & the notification is gone, time to actually remove it + mNotificationEntryManager.performRemoveNotification(bubble.entry.notification, + UNDEFINED_DISMISS_REASON); + } else { + // The notification is still in the shade but we've removed the bubble so + // lets make sure NoMan knows it's not a bubble anymore + try { + mBarService.onNotificationBubbleChanged(bubble.getKey(), + false /* isBubble */); + } catch (RemoteException e) { + // Bad things have happened + } } } - } - public void onBubbleUpdated(Bubble bubble) { - if (mStackView != null) { - mStackView.updateBubble(bubble); + if (update.updatedBubble != null) { + mStackView.updateBubble(update.updatedBubble); } - } - @Override - public void onOrderChanged(List<Bubble> bubbles) { - if (mStackView != null) { - mStackView.updateBubbleOrder(bubbles); + if (update.orderChanged) { + mStackView.updateBubbleOrder(update.bubbles); } - } - @Override - public void onSelectionChanged(@Nullable Bubble selectedBubble) { - if (mStackView != null) { - mStackView.setSelectedBubble(selectedBubble); + if (update.selectionChanged) { + mStackView.setSelectedBubble(update.selectedBubble); } - } - @Override - public void onExpandedChanged(boolean expanded) { - if (mStackView != null) { - mStackView.setExpanded(expanded); + // Expanding? Apply this last. + if (update.expandedChanged && update.expanded) { + mStackView.setExpanded(true); } - } - // Runs on state change. - @Override - public void apply() { mNotificationEntryManager.updateNotifications(); updateStack(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 1858244d13bc..6ab973eb3065 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -60,54 +60,46 @@ public class BubbleData { 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; + boolean selectionChanged; + boolean orderChanged; + boolean expanded; + @Nullable Bubble selectedBubble; + @Nullable Bubble addedBubble; + @Nullable Bubble updatedBubble; + // Pair with Bubble and @DismissReason Integer + final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>(); + + // A read-only view of the bubbles list, changes there will be reflected here. + final List<Bubble> bubbles; + + private Update(List<Bubble> bubbleOrder) { + bubbles = Collections.unmodifiableList(bubbleOrder); + } + + boolean anythingChanged() { + return expandedChanged + || selectionChanged + || addedBubble != null + || updatedBubble != null + || !removedBubbles.isEmpty() + || orderChanged; + } + + void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) { + removedBubbles.add(new Pair<>(bubbleToRemove, reason)); + } + } + /** * This interface reports changes to the state and appearance of bubbles which should be applied * as necessary to the UI. - * <p> - * Each operation is a report of a pending operation. Each should be considered in - * combination, when {@link #apply()} is called. For example, both: onExpansionChanged, - * and onOrderChanged */ interface Listener { - - /** - * A new Bubble has been added. A call to {@link #onOrderChanged(List)} will - * follow, including the new Bubble in position - */ - void onBubbleAdded(Bubble bubble); - - /** - * A Bubble has been removed. A call to {@link #onOrderChanged(List)} will - * follow. - */ - void onBubbleRemoved(Bubble bubble, @DismissReason int reason); - - /** - * An existing bubble has been updated. - * - * @param bubble the bubble which was updated - */ - void onBubbleUpdated(Bubble bubble); - - /** - * Indicates that one or more bubbles should change position. This may be result of insert, - * or removal of a Bubble, in addition to re-sorting existing Bubbles. - * - * @param bubbles an immutable list of the bubbles in the new order - */ - void onOrderChanged(List<Bubble> bubbles); - - /** Indicates the selected bubble changed. */ - void onSelectionChanged(@Nullable Bubble selectedBubble); - - /** - * The UI should transition to the given state, incorporating any pending changes during - * the animation. - */ - void onExpandedChanged(boolean expanded); - - /** Commit any pending operations (since last call of apply()) */ - void apply(); + /** Reports changes have have occurred as a result of the most recent operation. */ + void applyUpdate(Update update); } interface TimeSource { @@ -115,17 +107,12 @@ public class BubbleData { } private final Context mContext; - private List<Bubble> mBubbles; + private final List<Bubble> mBubbles; private Bubble mSelectedBubble; private boolean mExpanded; // 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 Update mStateChange; private TimeSource mTimeSource = System::currentTimeMillis; @@ -136,6 +123,7 @@ public class BubbleData { public BubbleData(Context context) { mContext = context; mBubbles = new ArrayList<>(); + mStateChange = new Update(mBubbles); } public boolean hasBubbles() { @@ -185,7 +173,6 @@ public class BubbleData { // Updates an existing bubble bubble.setEntry(entry); doUpdate(bubble); - mUpdatedBubble = bubble; } if (shouldAutoExpand(entry)) { setSelectedBubbleInternal(bubble); @@ -217,11 +204,11 @@ public class BubbleData { minInsertPoint = newGroup ? 0 : findFirstIndexForGroup(bubble.getGroupId()); } if (insertBubble(minInsertPoint, bubble) < mBubbles.size() - 1) { - mOrderChanged = true; + mStateChange.orderChanged = true; } - mAddedBubble = bubble; + mStateChange.addedBubble = bubble; if (!isExpanded()) { - mOrderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId())); + mStateChange.orderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId())); // Top bubble becomes selected. setSelectedBubbleInternal(mBubbles.get(0)); } @@ -243,6 +230,7 @@ public class BubbleData { if (DEBUG) { Log.d(TAG, "doUpdate: " + bubble); } + mStateChange.updatedBubble = bubble; if (!isExpanded()) { // while collapsed, update causes re-pack int prevPos = mBubbles.indexOf(bubble); @@ -250,7 +238,7 @@ public class BubbleData { int newPos = insertBubble(0, bubble); if (prevPos != newPos) { packGroup(newPos); - mOrderChanged = true; + mStateChange.orderChanged = true; } setSelectedBubbleInternal(mBubbles.get(0)); } @@ -269,12 +257,12 @@ public class BubbleData { } if (indexToRemove < mBubbles.size() - 1) { // Removing anything but the last bubble means positions will change. - mOrderChanged = true; + mStateChange.orderChanged = true; } mBubbles.remove(indexToRemove); - mRemovedBubbles.add(Pair.create(bubbleToRemove, reason)); + mStateChange.bubbleRemoved(bubbleToRemove, reason); if (!isExpanded()) { - mOrderChanged |= repackAll(); + mStateChange.orderChanged |= repackAll(); } // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null. @@ -301,77 +289,20 @@ public class BubbleData { Bubble bubble = mBubbles.remove(0); bubble.setDismissed(); maybeSendDeleteIntent(reason, bubble.entry); - mRemovedBubbles.add(Pair.create(bubble, reason)); + mStateChange.bubbleRemoved(bubble, reason); } dispatchPendingChanges(); } - 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(); + if (mListener != null && mStateChange.anythingChanged()) { + mListener.applyUpdate(mStateChange); } + mStateChange = new Update(mBubbles); } /** - * Requests a change to the selected bubble. Calls {@link Listener#onSelectionChanged} if - * the value changes. + * Requests a change to the selected bubble. * * @param bubble the new selected bubble */ @@ -391,13 +322,12 @@ public class BubbleData { bubble.markAsAccessedAt(mTimeSource.currentTimeMillis()); } mSelectedBubble = bubble; - mSelectionChanged = true; - return; + mStateChange.selectedBubble = bubble; + mStateChange.selectionChanged = true; } /** - * Requests a change to the expanded state. Calls {@link Listener#onExpandedChanged} if - * the value changes. + * Requests a change to the expanded state. * * @param shouldExpand the new requested state */ @@ -418,11 +348,11 @@ public class BubbleData { return; } mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis()); - mOrderChanged |= repackAll(); + mStateChange.orderChanged |= repackAll(); } else if (!mBubbles.isEmpty()) { // Apply ordering and grouping rules from expanded -> collapsed, then save // the result. - mOrderChanged |= repackAll(); + mStateChange.orderChanged |= repackAll(); // Save the state which should be returned to when expanded (with no other changes) if (mBubbles.indexOf(mSelectedBubble) > 0) { @@ -442,7 +372,8 @@ public class BubbleData { } } mExpanded = shouldExpand; - mExpandedChanged = true; + mStateChange.expanded = shouldExpand; + mStateChange.expandedChanged = true; } private static long sortKey(Bubble bubble) { @@ -569,7 +500,8 @@ public class BubbleData { if (repacked.equals(mBubbles)) { return false; } - mBubbles = repacked; + mBubbles.clear(); + mBubbles.addAll(repacked); return true; } @@ -595,7 +527,7 @@ public class BubbleData { for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) { Bubble bubble = i.next(); if (bubble.getGroupId().equals(blockedGroupId)) { - mRemovedBubbles.add(Pair.create(bubble, BubbleController.DISMISS_BLOCKED)); + mStateChange.bubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED); i.remove(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 364a0f7ed2e9..815a70ae3026 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -16,19 +16,12 @@ package com.android.systemui.bubbles; -import static com.android.systemui.bubbles.BubbleController.DISMISS_AGED; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.Notification; @@ -38,6 +31,7 @@ import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Pair; import androidx.test.filters.SmallTest; @@ -51,11 +45,20 @@ import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.List; - +/** + * Tests operations and the resulting state managed by BubbleData. + * <p> + * After each operation to verify, {@link #verifyUpdateReceived()} ensures the listener was called + * and captures the Update object received there. + * <p> + * Other methods beginning with 'assert' access the captured update object and assert on specific + * aspects of it. + */ @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -90,6 +93,9 @@ public class BubbleDataTest extends SysuiTestCase { private NotificationTestHelper mNotificationTestHelper; + @Captor + private ArgumentCaptor<BubbleData.Update> mUpdateCaptor; + @Before public void setUp() throws Exception { mNotificationTestHelper = new NotificationTestHelper(mContext); @@ -132,9 +138,9 @@ public class BubbleDataTest extends SysuiTestCase { sendUpdatedEntryAtTime(mEntryA1, 1000); // Verify - verify(mListener).onBubbleAdded(eq(mBubbleA1)); - verify(mListener).onSelectionChanged(eq(mBubbleA1)); - verify(mListener).apply(); + verifyUpdateReceived(); + assertBubbleAdded(mBubbleA1); + assertSelectionChangedTo(mBubbleA1); } @Test @@ -149,8 +155,8 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); // Verify - verify(mListener).onBubbleRemoved(eq(mBubbleA1), eq(BubbleController.DISMISS_USER_GESTURE)); - verify(mListener).apply(); + verifyUpdateReceived(); + assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_USER_GESTURE); } // COLLAPSED / ADD @@ -171,7 +177,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryC1, 6000); - verify(mListener).onBubbleRemoved(eq(mBubbleA1), eq(DISMISS_AGED)); + verifyUpdateReceived(); + assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED); } /** @@ -190,19 +197,20 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryA1, 1000); - verify(mListener, never()).onOrderChanged(anyList()); + verifyUpdateReceived(); + assertOrderNotChanged(); - reset(mListener); sendUpdatedEntryAtTime(mEntryB1, 2000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB1, mBubbleA1); - reset(mListener); sendUpdatedEntryAtTime(mEntryB2, 3000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB2, mBubbleB1, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1); - reset(mListener); sendUpdatedEntryAtTime(mEntryA2, 4000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1); } /** @@ -223,19 +231,20 @@ public class BubbleDataTest extends SysuiTestCase { // Test setOngoing(mEntryA1, true); sendUpdatedEntryAtTime(mEntryA1, 1000); - verify(mListener, never()).onOrderChanged(anyList()); + verifyUpdateReceived(); + assertOrderNotChanged(); - reset(mListener); sendUpdatedEntryAtTime(mEntryB1, 2000); - verify(mListener, never()).onOrderChanged(eq(listOf(mBubbleA1, mBubbleB1))); + verifyUpdateReceived(); + assertOrderNotChanged(); - reset(mListener); sendUpdatedEntryAtTime(mEntryB2, 3000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleB2, mBubbleB1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleA1, mBubbleB2, mBubbleB1); - reset(mListener); sendUpdatedEntryAtTime(mEntryA2, 4000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1); } /** @@ -252,20 +261,22 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryA1, 1000); - verify(mListener).onSelectionChanged(eq(mBubbleA1)); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleA1); - reset(mListener); sendUpdatedEntryAtTime(mEntryB1, 2000); - verify(mListener).onSelectionChanged(eq(mBubbleB1)); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleB1); - reset(mListener); sendUpdatedEntryAtTime(mEntryB2, 3000); - verify(mListener).onSelectionChanged(eq(mBubbleB2)); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleB2); - reset(mListener); sendUpdatedEntryAtTime(mEntryA2, 4000); - verify(mListener).onSelectionChanged(eq(mBubbleA2)); + verifyUpdateReceived(); + 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. @@ -282,9 +293,17 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryB1, 2000); + verifyUpdateReceived(); + assertSelectionNotChanged(); + sendUpdatedEntryAtTime(mEntryB2, 3000); + verifyUpdateReceived(); + assertSelectionNotChanged(); + sendUpdatedEntryAtTime(mEntryA2, 4000); - verify(mListener, never()).onSelectionChanged(any(Bubble.class)); + verifyUpdateReceived(); + assertSelectionNotChanged(); + assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1); // selection unchanged } @@ -305,7 +324,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB2, mBubbleB1, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1); } @@ -324,7 +344,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE); - verify(mListener, never()).onOrderChanged(anyList()); + verifyUpdateReceived(); + assertOrderNotChanged(); } /** @@ -343,7 +364,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB2, mBubbleB1, mBubbleA2))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2); } /** @@ -361,7 +383,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL); - verify(mListener).onSelectionChanged(eq(mBubbleB2)); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleB2); } // COLLAPSED / UPDATE @@ -381,11 +404,12 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryB1, 5000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1); - reset(mListener); sendUpdatedEntryAtTime(mEntryA1, 6000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2); } /** @@ -402,11 +426,12 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryB1, 5000); - verify(mListener).onSelectionChanged(eq(mBubbleB1)); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleB1); - reset(mListener); sendUpdatedEntryAtTime(mEntryA1, 6000); - verify(mListener).onSelectionChanged(eq(mBubbleA1)); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleA1); } /** @@ -425,7 +450,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryB2, 5000); // [A1*, A2, B2, B1] - verify(mListener, never()).onSelectionChanged(any(Bubble.class)); + verifyUpdateReceived(); + assertSelectionNotChanged(); } /** @@ -434,10 +460,10 @@ public class BubbleDataTest extends SysuiTestCase { @Test public void test_collapsed_expansion_whenEmpty_doesNothing() { assertThat(mBubbleData.hasBubbles()).isFalse(); - changeExpandedStateAtTime(true, 2000L); + mBubbleData.setListener(mListener); - verify(mListener, never()).onExpandedChanged(anyBoolean()); - verify(mListener, never()).apply(); + changeExpandedStateAtTime(true, 2000L); + verifyZeroInteractions(mListener); } @Test @@ -450,7 +476,8 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); // Verify the selection was cleared. - verify(mListener).onSelectionChanged(isNull()); + verifyUpdateReceived(); + assertSelectionCleared(); } // EXPANDED / ADD @@ -476,7 +503,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryC1, 4000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1); } /** @@ -498,7 +526,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryC1, 4000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleA2, mBubbleC1, mBubbleB1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleC1, mBubbleB1); } /** @@ -519,7 +548,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryA3, 4000); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1); } // EXPANDED / UPDATE @@ -543,7 +573,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryA1, 4000); - verify(mListener, never()).onOrderChanged(anyList()); + verifyUpdateReceived(); + assertOrderNotChanged(); } /** @@ -564,9 +595,16 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryA1, 6000); + verifyUpdateReceived(); + assertOrderNotChanged(); + sendUpdatedEntryAtTime(mEntryA2, 7000); + verifyUpdateReceived(); + assertOrderNotChanged(); + sendUpdatedEntryAtTime(mEntryB1, 8000); - verify(mListener, never()).onSelectionChanged(any(Bubble.class)); + verifyUpdateReceived(); + assertOrderNotChanged(); } // EXPANDED / REMOVE @@ -590,7 +628,8 @@ public class BubbleDataTest extends SysuiTestCase { // Test mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleA2, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1); } /** @@ -614,11 +653,12 @@ public class BubbleDataTest extends SysuiTestCase { // Test mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE); - verify(mListener).onSelectionChanged(mBubbleA1); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleA1); - reset(mListener); mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); - verify(mListener).onSelectionChanged(mBubbleB1); + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleB1); } @Test @@ -629,11 +669,12 @@ public class BubbleDataTest extends SysuiTestCase { // Test changeExpandedStateAtTime(true, 3000L); - verify(mListener).onExpandedChanged(eq(true)); + verifyUpdateReceived(); + assertExpandedChangedTo(true); - reset(mListener); changeExpandedStateAtTime(false, 4000L); - verify(mListener).onExpandedChanged(eq(false)); + verifyUpdateReceived(); + assertExpandedChangedTo(false); } /** @@ -663,7 +704,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setSelectedBubble(mBubbleA2); mBubbleData.setListener(mListener); assertThat(mBubbleData.getBubbles()).isEqualTo( - listOf(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1)); + ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1)); // Test @@ -678,12 +719,13 @@ public class BubbleDataTest extends SysuiTestCase { // // collapse -> selected bubble (A2) moves first. changeExpandedStateAtTime(false, 8000L); - verify(mListener).onOrderChanged(eq(listOf(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2); // expand -> "original" order/grouping restored - reset(mListener); changeExpandedStateAtTime(true, 10000L); - verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1); } /** @@ -717,15 +759,17 @@ public class BubbleDataTest extends SysuiTestCase { // // collapse -> selected bubble (A2) moves first. changeExpandedStateAtTime(false, 8000L); - verify(mListener).onOrderChanged(eq(listOf(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2))); + verifyUpdateReceived(); + assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2); // An update occurs, which causes sorting, and this invalidates the previously saved order. sendUpdatedEntryAtTime(mEntryA2, 9000); + verifyUpdateReceived(); // No order changes when expanding because the new sorted order remains. - reset(mListener); changeExpandedStateAtTime(true, 10000L); - verify(mListener, never()).onOrderChanged(anyList()); + verifyUpdateReceived(); + assertOrderNotChanged(); } @Test @@ -737,9 +781,61 @@ public class BubbleDataTest extends SysuiTestCase { // Test mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); - verify(mListener).onExpandedChanged(eq(false)); + verifyUpdateReceived(); + assertExpandedChangedTo(false); } + private void verifyUpdateReceived() { + verify(mListener).applyUpdate(mUpdateCaptor.capture()); + reset(mListener); + } + + private void assertBubbleAdded(Bubble expected) { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.addedBubble).named("addedBubble").isEqualTo(expected); + } + + private void assertBubbleRemoved(Bubble expected, @BubbleController.DismissReason int reason) { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.removedBubbles).named("removedBubbles") + .isEqualTo(ImmutableList.of(Pair.create(expected, reason))); + } + + private void assertOrderNotChanged() { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.orderChanged).named("orderChanged").isFalse(); + } + + private void assertOrderChangedTo(Bubble... order) { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.orderChanged).named("orderChanged").isTrue(); + assertThat(update.bubbles).named("bubble order").isEqualTo(ImmutableList.copyOf(order)); + } + + private void assertSelectionNotChanged() { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.selectionChanged).named("selectionChanged").isFalse(); + } + + private void assertSelectionChangedTo(Bubble bubble) { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.selectionChanged).named("selectionChanged").isTrue(); + assertThat(update.selectedBubble).named("selectedBubble").isEqualTo(bubble); + } + + private void assertSelectionCleared() { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.selectionChanged).named("selectionChanged").isTrue(); + assertThat(update.selectedBubble).named("selectedBubble").isNull(); + } + + private void assertExpandedChangedTo(boolean expected) { + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.expandedChanged).named("expandedChanged").isTrue(); + assertThat(update.expanded).named("expanded").isEqualTo(expected); + } + + private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) { return createBubbleEntry(userId, notifKey, packageName, 1000); } @@ -798,9 +894,4 @@ public class BubbleDataTest extends SysuiTestCase { setCurrentTime(time); mBubbleData.setExpanded(shouldBeExpanded); } - - /** Syntactic sugar to keep assertions more readable */ - private static <T> List<T> listOf(T... a) { - return ImmutableList.copyOf(a); - } }
\ No newline at end of file |