From bac16b93bca72458f4f1da7d17bdf21fd028a94c Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Mon, 1 Feb 2021 11:23:30 -0800 Subject: [PATCH] Sysui bubbles multiuser fixes: persistence / overflow * Persist bubbles per-user - rather than one list the XML now has a list per-user. The entries in these lists still include userId for workprofile since bubbles are mixed in the stack / overflow for workprofile. * When loading bubbles, only the ones for the current user are loaded / hit bubbleController code * When user changes, overflow data should be re-loaded * Allow the bubble window to be visible for all users Test: atest BubbleXmlHelperTest BubbleVolatileRepositoryTest BubblePersistentRepositoryTest BubblesTest Bug: 173408780 Change-Id: I88cb7cc7ee676d8e0756328a95a54fdaf018a013 --- .../wm/shell/bubbles/BubbleController.java | 53 +++- .../android/wm/shell/bubbles/BubbleData.java | 13 +- .../wm/shell/bubbles/BubbleDataRepository.kt | 51 ++-- .../wm/shell/bubbles/BubbleViewInfoTask.java | 14 +- .../com/android/wm/shell/bubbles/Bubbles.java | 9 + .../storage/BubblePersistentRepository.kt | 13 +- .../storage/BubbleVolatileRepository.kt | 45 ++- .../shell/bubbles/storage/BubbleXmlHelper.kt | 50 ++- .../storage/BubblePersistentRepositoryTest.kt | 33 +- .../storage/BubbleVolatileRepositoryTest.kt | 93 ++++-- .../bubbles/storage/BubbleXmlHelperTest.kt | 150 ++++++--- .../systemui/wmshell/BubblesManager.java | 8 + .../row/NotificationTestHelper.java | 108 ++++--- .../android/systemui/wmshell/BubblesTest.java | 289 ++++++++++-------- .../wmshell/NewNotifPipelineBubblesTest.java | 184 +++++------ 15 files changed, 724 insertions(+), 389 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index dca5985184320..4b037214fcdd2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -48,6 +48,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.PointF; @@ -64,6 +65,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseSetArray; import android.view.View; import android.view.ViewGroup; @@ -144,6 +146,8 @@ public class BubbleController { // Tracks the id of the current (foreground) user. private int mCurrentUserId; + // Current profiles of the user (e.g. user with a workprofile) + private SparseArray mCurrentProfiles; // Saves notification keys of active bubbles when users are switched. private final SparseSetArray mSavedBubbleKeysPerUser; @@ -153,8 +157,8 @@ public class BubbleController { // Callback that updates BubbleOverflowActivity on data change. @Nullable private BubbleData.Listener mOverflowListener = null; - // Only load overflow data from disk once - private boolean mOverflowDataLoaded = false; + // Typically only load once & after user switches + private boolean mOverflowDataLoadNeeded = true; /** * When the shade status changes to SHADE (from anything but SHADE, like LOCKED) we'll select @@ -468,14 +472,31 @@ public class BubbleController { updateStack(); } - private void onUserChanged(int newUserId) { + /** Called when the current user changes. */ + @VisibleForTesting + public void onUserChanged(int newUserId) { saveBubbles(mCurrentUserId); - mBubbleData.dismissAll(DISMISS_USER_CHANGED); - restoreBubbles(newUserId); mCurrentUserId = newUserId; + + mBubbleData.dismissAll(DISMISS_USER_CHANGED); + mBubbleData.clearOverflow(); + mOverflowDataLoadNeeded = true; + + restoreBubbles(newUserId); mBubbleData.setCurrentUserId(newUserId); } + /** Called when the profiles for the current user change. **/ + public void onCurrentProfilesChanged(SparseArray currentProfiles) { + mCurrentProfiles = currentProfiles; + } + + /** Whether this userId belongs to the current user. */ + private boolean isCurrentProfile(int userId) { + return userId == UserHandle.USER_ALL + || (mCurrentProfiles != null && mCurrentProfiles.get(userId) != null); + } + /** * Sets whether to perform inflation on the same thread as the caller. This method should only * be used in tests, not in production. @@ -556,6 +577,7 @@ public class BubbleController { mWmLayoutParams.setTitle("Bubbles!"); mWmLayoutParams.packageName = mContext.getPackageName(); mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mWmLayoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; try { mAddedToWindowManager = true; @@ -639,7 +661,7 @@ public class BubbleController { }); }); // Finally, remove the entries for this user now that bubbles are restored. - mSavedBubbleKeysPerUser.remove(mCurrentUserId); + mSavedBubbleKeysPerUser.remove(userId); } private void updateForThemeChanges() { @@ -804,12 +826,12 @@ public class BubbleController { * Fills the overflow bubbles by loading them from disk. */ void loadOverflowBubblesFromDisk() { - if (!mBubbleData.getOverflowBubbles().isEmpty() || mOverflowDataLoaded) { + if (!mBubbleData.getOverflowBubbles().isEmpty() && !mOverflowDataLoadNeeded) { // we don't need to load overflow bubbles from disk if it is already in memory return; } - mOverflowDataLoaded = true; - mDataRepository.loadBubbles((bubbles) -> { + mOverflowDataLoadNeeded = false; + mDataRepository.loadBubbles(mCurrentUserId, (bubbles) -> { bubbles.forEach(bubble -> { if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) { // if the bubble is already active, there's no need to push it to overflow @@ -911,6 +933,12 @@ public class BubbleController { Pair entryData = entryDataByKey.get(key); BubbleEntry entry = entryData.first; boolean shouldBubbleUp = entryData.second; + + if (entry != null && !isCurrentProfile( + entry.getStatusBarNotification().getUser().getIdentifier())) { + return; + } + rankingMap.getRanking(key, mTmpRanking); boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); if (isActiveBubble && !mTmpRanking.canBubble()) { @@ -1427,6 +1455,13 @@ public class BubbleController { }); } + @Override + public void onCurrentProfilesChanged(SparseArray currentProfiles) { + mMainExecutor.execute(() -> { + BubbleController.this.onCurrentProfilesChanged(currentProfiles); + }); + } + @Override public void onConfigChanged(Configuration newConfig) { mMainExecutor.execute(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index f6e6b8f3b7002..8434d668e153c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -510,7 +510,8 @@ public class BubbleData { || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE || reason == Bubbles.DISMISS_BLOCKED || reason == Bubbles.DISMISS_SHORTCUT_REMOVED - || reason == Bubbles.DISMISS_PACKAGE_REMOVED)) { + || reason == Bubbles.DISMISS_PACKAGE_REMOVED + || reason == Bubbles.DISMISS_USER_CHANGED)) { Bubble b = getOverflowBubbleWithKey(key); if (DEBUG_BUBBLE_DATA) { @@ -642,6 +643,16 @@ public class BubbleData { } } + /** + * Removes all bubbles from the overflow, called when the user changes. + */ + public void clearOverflow() { + while (!mOverflowBubbles.isEmpty()) { + doRemove(mOverflowBubbles.get(0).getKey(), Bubbles.DISMISS_USER_CHANGED); + } + dispatchPendingChanges(); + } + private void dispatchPendingChanges() { if (mListener != null && mStateChange.anythingChanged()) { mListener.applyUpdate(mStateChange); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index bfacd1cfe90e8..9d9e442affd34 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -58,7 +58,8 @@ internal class BubbleDataRepository( */ fun addBubbles(@UserIdInt userId: Int, bubbles: List) { if (DEBUG) Log.d(TAG, "adding ${bubbles.size} bubbles") - val entities = transform(userId, bubbles).also(volatileRepository::addBubbles) + val entities = transform(bubbles).also { + b -> volatileRepository.addBubbles(userId, b) } if (entities.isNotEmpty()) persistToDisk() } @@ -67,14 +68,15 @@ internal class BubbleDataRepository( */ fun removeBubbles(@UserIdInt userId: Int, bubbles: List) { if (DEBUG) Log.d(TAG, "removing ${bubbles.size} bubbles") - val entities = transform(userId, bubbles).also(volatileRepository::removeBubbles) + val entities = transform(bubbles).also { + b -> volatileRepository.removeBubbles(userId, b) } if (entities.isNotEmpty()) persistToDisk() } - private fun transform(userId: Int, bubbles: List): List { + private fun transform(bubbles: List): List { return bubbles.mapNotNull { b -> BubbleEntity( - userId, + b.user.identifier, b.packageName, b.metadataShortcutId ?: return@mapNotNull null, b.key, @@ -116,10 +118,11 @@ internal class BubbleDataRepository( /** * Load bubbles from disk. * @param cb The callback to be run after the bubbles are loaded. This callback is always made - * on the main thread of the hosting process. + * on the main thread of the hosting process. The callback is only run if there are + * bubbles. */ @SuppressLint("WrongConstant") - fun loadBubbles(cb: (List) -> Unit) = ioScope.launch { + fun loadBubbles(userId: Int, cb: (List) -> Unit) = ioScope.launch { /** * Load BubbleEntity from disk. * e.g. @@ -129,8 +132,9 @@ internal class BubbleDataRepository( * BubbleEntity(0, "com.example.messenger", "id-1") * ] */ - val entities = persistentRepository.readFromDisk() - volatileRepository.addBubbles(entities) + val entitiesByUser = persistentRepository.readFromDisk() + val entities = entitiesByUser.get(userId) ?: return@launch + volatileRepository.addBubbles(userId, entities) /** * Extract userId/packageName from these entities. * e.g. @@ -139,9 +143,10 @@ internal class BubbleDataRepository( * ] */ val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet() + /** - * Retrieve shortcuts with given userId/packageName combination, then construct a mapping - * from the userId/packageName pair to a list of associated ShortcutInfo. + * Retrieve shortcuts with given userId/packageName combination, then construct a + * mapping from the userId/packageName pair to a list of associated ShortcutInfo. * e.g. * { * ShortcutKey(0, "com.example.messenger") -> [ @@ -161,21 +166,23 @@ internal class BubbleDataRepository( .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId)) ?: emptyList() }.groupBy { ShortcutKey(it.userId, it.`package`) } - // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them - // into Bubble. + // For each entity loaded from xml, find the corresponding ShortcutInfo then convert + // them into Bubble. val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.firstOrNull { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble( - entity.key, - shortcutInfo, - entity.desiredHeight, - entity.desiredHeightResId, - entity.title, - entity.taskId, - entity.locus, - mainExecutor - ) } + ?.let { shortcutInfo -> + Bubble( + entity.key, + shortcutInfo, + entity.desiredHeight, + entity.desiredHeightResId, + entity.title, + entity.taskId, + entity.locus, + mainExecutor + ) + } } mainExecutor.execute { cb(bubbles) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index c5a712e271e4f..5cdce886a9e8b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -217,10 +217,16 @@ public class BubbleViewInfoTask extends AsyncTask currentProfiles); + /** * Called when config changed. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt index 66a75af7d64c9..130790a041608 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles.storage import android.content.Context import android.util.AtomicFile import android.util.Log +import android.util.SparseArray import java.io.File import java.io.FileOutputStream import java.io.IOException @@ -27,8 +28,8 @@ class BubblePersistentRepository(context: Context) { private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir, "overflow_bubbles.xml"), "overflow-bubbles") - fun persistsToDisk(bubbles: List): Boolean { - if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles") + fun persistsToDisk(bubbles: SparseArray>): Boolean { + if (DEBUG) Log.d(TAG, "persisting ${bubbles.size()} bubbles") synchronized(bubbleFile) { val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) { Log.e(TAG, "Failed to save bubble file", e) @@ -37,7 +38,7 @@ class BubblePersistentRepository(context: Context) { try { writeXml(stream, bubbles) bubbleFile.finishWrite(stream) - if (DEBUG) Log.d(TAG, "persisted ${bubbles.size} bubbles") + if (DEBUG) Log.d(TAG, "persisted ${bubbles.size()} bubbles") return true } catch (e: Exception) { Log.e(TAG, "Failed to save bubble file, restoring backup", e) @@ -47,13 +48,13 @@ class BubblePersistentRepository(context: Context) { return false } - fun readFromDisk(): List { + fun readFromDisk(): SparseArray> { synchronized(bubbleFile) { - if (!bubbleFile.exists()) return emptyList() + if (!bubbleFile.exists()) return SparseArray() try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) { Log.e(TAG, "Failed to open bubble file", e) } - return emptyList() + return SparseArray() } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt index 7f0b165bdc25e..a5267d8be9fe0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles.storage import android.content.pm.LauncherApps import android.os.UserHandle +import android.util.SparseArray import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.bubbles.ShortcutKey @@ -27,10 +28,11 @@ private const val CAPACITY = 16 * manipulation. */ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { + /** - * An ordered set of bubbles based on their natural ordering. + * Set of bubbles per user. Each set of bubbles is ordered by recency. */ - private var entities = mutableSetOf() + private var entitiesByUser = SparseArray>() /** * The capacity of the cache. @@ -39,19 +41,43 @@ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { var capacity = CAPACITY /** - * Returns a snapshot of all the bubbles. + * Returns a snapshot of all the bubbles, a map of the userId to bubble list. */ - val bubbles: List + val bubbles: SparseArray> @Synchronized - get() = entities.toList() + get() { + val map = SparseArray>() + for (i in 0 until entitiesByUser.size()) { + val k = entitiesByUser.keyAt(i) + val v = entitiesByUser.valueAt(i) + map.put(k, v.toList()) + } + return map + } + + /** + * Returns the entity list of the provided user's bubbles or creates one if it doesn't exist. + */ + @Synchronized + fun getEntities(userId: Int): MutableList { + val entities = entitiesByUser.get(userId) + return when (entities) { + null -> mutableListOf().also { + entitiesByUser.put(userId, it) + } + else -> entities + } + } /** * Add the bubbles to memory and perform a de-duplication. In case a bubble already exists, * it will be moved to the last. */ @Synchronized - fun addBubbles(bubbles: List) { + fun addBubbles(userId: Int, bubbles: List) { if (bubbles.isEmpty()) return + // Get the list for this user + var entities = getEntities(userId) // Verify the size of given bubbles is within capacity, otherwise trim down to capacity val bubblesInRange = bubbles.takeLast(capacity) // To ensure natural ordering of the bubbles, removes bubbles which already exist @@ -61,16 +87,17 @@ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { if (overflowCount > 0) { // Uncache ShortcutInfo of bubbles that will be removed due to capacity uncache(entities.take(overflowCount)) - entities = entities.drop(overflowCount).toMutableSet() + entities = entities.drop(overflowCount).toMutableList() } entities.addAll(bubblesInRange) + entitiesByUser.put(userId, entities) cache(uniqueBubbles) } @Synchronized - fun removeBubbles(bubbles: List) = + fun removeBubbles(userId: Int, bubbles: List) = uncache(bubbles.filter { b: BubbleEntity -> - entities.removeIf { e: BubbleEntity -> b.key == e.key } }) + getEntities(userId).removeIf { e: BubbleEntity -> b.key == e.key } }) private fun cache(bubbles: List) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt index a74445bba1abf..f4fa1835b7a52 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles.storage import android.app.ActivityTaskManager.INVALID_TASK_ID +import android.os.UserHandle +import android.util.SparseArray import android.util.Xml import com.android.internal.util.FastXmlSerializer import com.android.internal.util.XmlUtils @@ -26,8 +28,8 @@ import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets -// TODO: handle version changes gracefully -private const val CURRENT_VERSION = 1 +// If this number increases, consider bubbles might be restored even with differences in XML. +private const val CURRENT_VERSION = 2 private const val TAG_BUBBLES = "bs" private const val ATTR_VERSION = "v" @@ -46,13 +48,20 @@ private const val ATTR_LOCUS = "l" * Writes the bubbles in xml format into given output stream. */ @Throws(IOException::class) -fun writeXml(stream: OutputStream, bubbles: List) { +fun writeXml(stream: OutputStream, bubbles: SparseArray>) { val serializer: XmlSerializer = FastXmlSerializer() serializer.setOutput(stream, StandardCharsets.UTF_8.name()) serializer.startDocument(null, true) serializer.startTag(null, TAG_BUBBLES) serializer.attribute(null, ATTR_VERSION, CURRENT_VERSION.toString()) - bubbles.forEach { b -> writeXmlEntry(serializer, b) } + for (i in 0 until bubbles.size()) { + val k = bubbles.keyAt(i) + val v = bubbles.valueAt(i) + serializer.startTag(null, TAG_BUBBLES) + serializer.attribute(null, ATTR_USER_ID, k.toString()) + v.forEach { b -> writeXmlEntry(serializer, b) } + serializer.endTag(null, TAG_BUBBLES) + } serializer.endTag(null, TAG_BUBBLES) serializer.endDocument() } @@ -84,16 +93,39 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { /** * Reads the bubbles from xml file. */ -fun readXml(stream: InputStream): List { - val bubbles = mutableListOf() +fun readXml(stream: InputStream): SparseArray> { + val bubbles = SparseArray>() val parser: XmlPullParser = Xml.newPullParser() parser.setInput(stream, StandardCharsets.UTF_8.name()) XmlUtils.beginDocument(parser, TAG_BUBBLES) - val version = parser.getAttributeWithName(ATTR_VERSION)?.toInt() - if (version != null && version == CURRENT_VERSION) { + val veryOuterDepth = parser.depth + val version = parser.getAttributeWithName(ATTR_VERSION)?.toInt() ?: return bubbles + if (version == CURRENT_VERSION) { + while (XmlUtils.nextElementWithin(parser, veryOuterDepth)) { + val uid = parser.getAttributeWithName(ATTR_USER_ID) ?: continue + val outerDepth = parser.depth + val userBubbles = mutableListOf() + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + userBubbles.add(readXmlEntry(parser) ?: continue) + } + if (!userBubbles.isEmpty()) { + bubbles.put(uid.toInt(), userBubbles.toList()) + } + } + } else if (version == 1) { + // upgrade v1 to v2 format val outerDepth = parser.depth + val userBubbles = mutableListOf() while (XmlUtils.nextElementWithin(parser, outerDepth)) { - bubbles.add(readXmlEntry(parser) ?: continue) + // We can't tell which profile the bubble was for, so we'll only copy the main users' + // bubbles on upgrade. + val b = readXmlEntry(parser) + if (b != null && b.userId == UserHandle.USER_SYSTEM) { + userBubbles.add(b) + } + } + if (!userBubbles.isEmpty()) { + bubbles.put(UserHandle.USER_SYSTEM, userBubbles.toList()) } } return bubbles diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt index 2f064ac95204f..0972cf2c032fc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -18,8 +18,10 @@ package com.android.wm.shell.bubbles.storage import android.app.ActivityTaskManager.INVALID_TASK_ID import android.testing.AndroidTestingRunner +import android.util.SparseArray import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.bubbles.storage.BubbleXmlHelperTest.Companion.sparseArraysEqual import junit.framework.Assert.assertEquals import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertTrue @@ -31,19 +33,32 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class BubblePersistentRepositoryTest : ShellTestCase() { - private val bubbles = listOf( - // user, package, shortcut, notification key, height, res-height, title, taskId, locusId - BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0, null, 1, null), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title", - 2, null), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0, null, - INVALID_TASK_ID, "key-3") + // user, package, shortcut, notification key, height, res-height, title, taskId, locusId + private val user0Bubbles = listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null), + BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2, + null), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null, + INVALID_TASK_ID, null) ) + + private val user1Bubbles = listOf( + BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null), + BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4, + null), + BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null, + INVALID_TASK_ID, null) + ) + + private val bubbles = SparseArray>() + private lateinit var repository: BubblePersistentRepository @Before fun setup() { repository = BubblePersistentRepository(mContext) + bubbles.put(0, user0Bubbles) + bubbles.put(1, user1Bubbles) } @Test @@ -51,9 +66,9 @@ class BubblePersistentRepositoryTest : ShellTestCase() { // Verify read before write doesn't cause FileNotFoundException val actual = repository.readFromDisk() assertNotNull(actual) - assertTrue(actual.isEmpty()) + assertEquals(actual.size(), 0) repository.persistsToDisk(bubbles) - assertEquals(bubbles, repository.readFromDisk()) + assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk())) } } \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt index 03aa6c2eba12d..bfdf5208bbf09 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -21,32 +21,40 @@ import android.content.pm.LauncherApps import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import org.junit.Test import com.android.wm.shell.ShellTestCase import junit.framework.Assert.assertEquals import org.junit.Before -import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.reset @SmallTest @RunWith(AndroidTestingRunner::class) class BubbleVolatileRepositoryTest : ShellTestCase() { private val user0 = UserHandle.of(0) - private val user10 = UserHandle.of(10) + private val user10_managed = UserHandle.of(10) // In test, acts as workprofile of user0 + private val user11 = UserHandle.of(11) // user, package, shortcut, notification key, height, res-height, title, taskId, locusId - private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0, - null, 1, null) + private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", + "0key-1", 120, 0, null, 1, null) private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", - "key-2", 0, 16537428, "title", 2, null) - private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0, - null, INVALID_TASK_ID, "key-3") + "10key-2", 0, 16537428, "title", 2, null) + private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", + "0key-3", 120, 0, null, INVALID_TASK_ID, null) - private val bubbles = listOf(bubble1, bubble2, bubble3) + private val bubble11 = BubbleEntity(11, "com.example.messenger", + "shortcut-1", "01key-1", 120, 0, null, 3) + private val bubble12 = BubbleEntity(11, "com.example.chat", "alice and bob", + "11key-2", 0, 16537428, "title", INVALID_TASK_ID) + + private val user0bubbles = listOf(bubble1, bubble2, bubble3) + private val user11bubbles = listOf(bubble11, bubble12) private lateinit var repository: BubbleVolatileRepository private lateinit var launcherApps: LauncherApps @@ -59,51 +67,74 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { @Test fun testAddBubbles() { - repository.addBubbles(bubbles) - assertEquals(bubbles, repository.bubbles) + repository.addBubbles(user0.identifier, user0bubbles) + repository.addBubbles(user11.identifier, user11bubbles) + + assertEquals(user0bubbles, repository.getEntities(user0.identifier).toList()) + assertEquals(user11bubbles, repository.getEntities(user11.identifier).toList()) + verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-1", "shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) verify(launcherApps).cacheShortcuts(eq(PKG_CHAT), - eq(listOf("alice and bob")), eq(user10), + eq(listOf("alice and bob")), eq(user10_managed), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) - repository.addBubbles(listOf(bubble1)) - assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles) - verifyNoMoreInteractions(launcherApps) + verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER), + eq(listOf("shortcut-1")), eq(user11), + eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) + verify(launcherApps).cacheShortcuts(eq(PKG_CHAT), + eq(listOf("alice and bob")), eq(user11), + eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) + + repository.addBubbles(user0.identifier, listOf(bubble1)) + assertEquals(listOf(bubble2, bubble3, bubble1), repository.getEntities(user0.identifier)) + + repository.addBubbles(user11.identifier, listOf(bubble12)) + assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier)) + + Mockito.verifyNoMoreInteractions(launcherApps) } @Test fun testRemoveBubbles() { - repository.addBubbles(bubbles) - assertEquals(bubbles, repository.bubbles) + repository.addBubbles(user0.identifier, user0bubbles) + repository.addBubbles(user11.identifier, user11bubbles) - repository.removeBubbles(listOf(bubble3)) - assertEquals(listOf(bubble1, bubble2), repository.bubbles) + repository.removeBubbles(user0.identifier, listOf(bubble3)) + assertEquals(listOf(bubble1, bubble2), repository.getEntities(user0.identifier).toList()) verify(launcherApps).uncacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) + + reset(launcherApps) + + repository.removeBubbles(user11.identifier, listOf(bubble12)) + assertEquals(listOf(bubble11), repository.getEntities(user11.identifier).toList()) + verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT), + eq(listOf("alice and bob")), eq(user11), + eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } @Test fun testAddAndRemoveBubblesWhenExceedingCapacity() { repository.capacity = 2 // push bubbles beyond capacity - repository.addBubbles(bubbles) + repository.addBubbles(user0.identifier, user0bubbles) // verify it is trim down to capacity - assertEquals(listOf(bubble2, bubble3), repository.bubbles) + assertEquals(listOf(bubble2, bubble3), repository.getEntities(user0.identifier).toList()) verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) verify(launcherApps).cacheShortcuts(eq(PKG_CHAT), - eq(listOf("alice and bob")), eq(user10), + eq(listOf("alice and bob")), eq(user10_managed), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) - repository.addBubbles(listOf(bubble1)) - // verify the oldest bubble is popped - assertEquals(listOf(bubble3, bubble1), repository.bubbles) + repository.addBubbles(user0.identifier, listOf(bubble1)) + // verify the oldest bubble is popped 2, 3 + assertEquals(listOf(bubble3, bubble1), repository.getEntities(user0.identifier).toList()) verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT), - eq(listOf("alice and bob")), eq(user10), + eq(listOf("alice and bob")), eq(user10_managed), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } @@ -111,14 +142,14 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { fun testAddBubbleMatchesByKey() { val bubble = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, "title", 1, null) - repository.addBubbles(listOf(bubble)) - assertEquals(bubble, repository.bubbles.get(0)) + repository.addBubbles(user0.identifier, listOf(bubble)) + assertEquals(bubble, repository.getEntities(user0.identifier).get(0)) // Same key as first bubble but different entry val bubbleModified = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, - "different title", 2, null) - repository.addBubbles(listOf(bubbleModified)) - assertEquals(bubbleModified, repository.bubbles.get(0)) + "different title", 2) + repository.addBubbles(user0.identifier, listOf(bubbleModified)) + assertEquals(bubbleModified, repository.getEntities(user0.identifier).get(0)) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt index 8d719e7a73782..4ab9f87dbbf6e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt @@ -18,10 +18,12 @@ package com.android.wm.shell.bubbles.storage import android.app.ActivityTaskManager.INVALID_TASK_ID import android.testing.AndroidTestingRunner +import android.util.SparseArray import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import java.io.ByteArrayInputStream @@ -31,21 +33,65 @@ import java.io.ByteArrayOutputStream @RunWith(AndroidTestingRunner::class) class BubbleXmlHelperTest : ShellTestCase() { - private val bubbles = listOf( - // user, package, shortcut, notification key, height, res-height, title, taskId, locusId - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, 1), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", - 2, null), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, + private val user0Bubbles = listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1), + BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2, + null), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null, INVALID_TASK_ID, "l3") ) + private val user1Bubbles = listOf( + BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3), + BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4, + null), + BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null, + INVALID_TASK_ID, "l4") + ) + + private val bubbles = SparseArray>() + + // Checks that the contents of the two sparse arrays are the same. + companion object { + fun sparseArraysEqual( + one: SparseArray>?, + two: SparseArray>? + ): Boolean { + if (one == null && two == null) return true + if ((one == null) != (two == null)) return false + if (one!!.size() != two!!.size()) return false + for (i in 0 until one.size()) { + val k1 = one.keyAt(i) + val v1 = one.valueAt(i) + val k2 = two.keyAt(i) + val v2 = two.valueAt(i) + if (k1 != k2 && v1 != v2) { + return false + } + } + return true + } + } + + @Before + fun setup() { + bubbles.put(0, user0Bubbles) + bubbles.put(1, user1Bubbles) + } + @Test fun testWriteXml() { val expectedEntries = """ - - - + + + + + + + + + + """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -59,19 +105,26 @@ class BubbleXmlHelperTest : ShellTestCase() { fun testReadXml() { val src = """ - - - - + + + + + + + + + + + """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual) + assertTrue("failed parsing bubbles from xml\n$src", sparseArraysEqual(bubbles, actual)) } - // TODO: We should handle upgrades gracefully but this is v1 + // V0 -> V1 happened prior to release / during dogfood so nothing is saved @Test - fun testUpgradeDropsPreviousData() { + fun testUpgradeFromV0DropsPreviousData() { val src = """ @@ -81,7 +134,7 @@ class BubbleXmlHelperTest : ShellTestCase() { """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", emptyList(), actual) + assertEquals("failed parsing bubbles from xml\n$src", 0, actual.size()) } /** @@ -91,24 +144,25 @@ class BubbleXmlHelperTest : ShellTestCase() { */ @Test fun testReadXMLWithoutTaskId() { - val expectedBubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, - INVALID_TASK_ID), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", - INVALID_TASK_ID), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, - INVALID_TASK_ID) - ) + val expectedBubbles = SparseArray>() + expectedBubbles.put(0, listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, + null, INVALID_TASK_ID), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, + null, INVALID_TASK_ID)) + ) val src = """ - + + - + """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", expectedBubbles, actual) + assertTrue("failed parsing bubbles from xml\n$src", + sparseArraysEqual(expectedBubbles, actual)) } /** @@ -117,23 +171,45 @@ class BubbleXmlHelperTest : ShellTestCase() { */ @Test fun testXMLWithoutLocusToLocus() { - val expectedBubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, - INVALID_TASK_ID, null), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", - INVALID_TASK_ID, null), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, - INVALID_TASK_ID, null) + val expectedBubbles = SparseArray>() + expectedBubbles.put(0, listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, + null, INVALID_TASK_ID), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, + null, INVALID_TASK_ID)) ) val src = """ - """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", expectedBubbles, actual) + assertTrue("failed parsing bubbles from xml\n$src", + sparseArraysEqual(expectedBubbles, actual)) + } + + @Test + fun testUpgradeToV2SavesPreviousData() { + val src = """ + + + + + + + + """.trimIndent() + val expectedBubbles = SparseArray>() + expectedBubbles.put(0, listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, + null, INVALID_TASK_ID, null), + BubbleEntity(0, "com.example.messenger", "shortcut-4", "k4", 0, 16537428, + null, INVALID_TASK_ID, null)) + ) + val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) + assertTrue("failed parsing bubbles from xml\n$src", + sparseArraysEqual(expectedBubbles, actual)) } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index c70100082424a..b823534c2813a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -38,6 +38,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.RemoteException; import android.os.ServiceManager; @@ -48,6 +49,7 @@ import android.service.notification.ZenModeConfig; import android.util.ArraySet; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import android.view.View; import androidx.annotation.NonNull; @@ -247,6 +249,12 @@ public class BubblesManager implements Dumpable { public void onUserChanged(int userId) { mBubbles.onUserChanged(userId); } + + @Override + public void onCurrentProfilesChanged(SparseArray currentProfiles) { + mBubbles.onCurrentProfilesChanged(currentProfiles); + } + }); mSysuiProxy = new Bubbles.SysuiProxy() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index b4a3393ab5db2..0bb66fc14553c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -236,49 +236,15 @@ public class NotificationTestHelper { return createRow(PKG, UID, USER_HANDLE, false /* isGroupSummary */, groupkey); } - /** - * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. - */ - public ExpandableNotificationRow createBubbleInGroup() - throws Exception { - return createBubble(makeBubbleMetadata(null), PKG, true); - } - /** * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. */ public ExpandableNotificationRow createBubble() throws Exception { - return createBubble(makeBubbleMetadata(null), PKG, false); - } - - /** - * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. - * - * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent} - */ - public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent) - throws Exception { - return createBubble(makeBubbleMetadata(deleteIntent), PKG, false); - } - - /** - * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. - * - * @param bubbleMetadata the {@link BubbleMetadata} to use - */ - public ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg) - throws Exception { - return createBubble(bubbleMetadata, pkg, false); - } - - private ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg, - boolean inGroup) - throws Exception { Notification n = createNotification(false /* isGroupSummary */, - inGroup ? GROUP_KEY : null /* groupKey */, bubbleMetadata); + null /* groupKey */, makeBubbleMetadata(null)); n.flags |= FLAG_BUBBLE; - ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE, + ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); modifyRanking(row.getEntry()) .setCanBubble(true) @@ -286,6 +252,76 @@ public class NotificationTestHelper { return row; } + /** + * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble and is part + * of a group of notifications. + */ + public ExpandableNotificationRow createBubbleInGroup() + throws Exception { + Notification n = createNotification(false /* isGroupSummary */, + GROUP_KEY /* groupKey */, makeBubbleMetadata(null)); + n.flags |= FLAG_BUBBLE; + ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE, + 0 /* extraInflationFlags */, IMPORTANCE_HIGH); + modifyRanking(row.getEntry()) + .setCanBubble(true) + .build(); + return row; + } + + /** + * Returns an {@link NotificationEntry} that should be shown as a bubble. + * + * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent} + */ + public NotificationEntry createBubble(@Nullable PendingIntent deleteIntent) { + return createBubble(makeBubbleMetadata(deleteIntent), USER_HANDLE); + } + + /** + * Returns an {@link NotificationEntry} that should be shown as a bubble. + * + * @param handle the user to associate with this bubble. + */ + public NotificationEntry createBubble(UserHandle handle) { + return createBubble(makeBubbleMetadata(null), handle); + } + + /** + * Returns an {@link NotificationEntry} that should be shown as a bubble. + * + * @param userHandle the user to associate with this notification. + */ + private NotificationEntry createBubble(BubbleMetadata metadata, UserHandle userHandle) { + Notification n = createNotification(false /* isGroupSummary */, null /* groupKey */, + metadata); + n.flags |= FLAG_BUBBLE; + + final NotificationChannel channel = + new NotificationChannel( + n.getChannelId(), + n.getChannelId(), + IMPORTANCE_HIGH); + channel.setBlockable(true); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(PKG) + .setOpPkg(PKG) + .setId(mId++) + .setUid(UID) + .setInitialPid(2000) + .setNotification(n) + .setUser(userHandle) + .setPostTime(System.currentTimeMillis()) + .setChannel(channel) + .build(); + + modifyRanking(entry) + .setCanBubble(true) + .build(); + return entry; + } + /** * Creates a notification row with the given details. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index abc8da24fdb5f..3e5fb7347b136 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -49,6 +49,7 @@ import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.face.FaceManager; import android.os.Handler; import android.os.PowerManager; +import android.os.UserHandle; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; @@ -172,14 +173,17 @@ public class BubblesTest extends SysuiTestCase { private NotificationRemoveInterceptor mRemoveInterceptor; private NotificationTestHelper mNotificationTestHelper; - private ExpandableNotificationRow mRow; - private ExpandableNotificationRow mRow2; - private ExpandableNotificationRow mRow3; + private NotificationEntry mRow; + private NotificationEntry mRow2; + private NotificationEntry mRow3; private ExpandableNotificationRow mNonBubbleNotifRow; private BubbleEntry mBubbleEntry; private BubbleEntry mBubbleEntry2; private BubbleEntry mBubbleEntry3; + private BubbleEntry mBubbleEntryUser11; + private BubbleEntry mBubbleEntry2User11; + @Mock private Bubbles.BubbleExpandListener mBubbleExpandListener; @Mock @@ -245,14 +249,20 @@ public class BubblesTest extends SysuiTestCase { mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mRow3 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); - mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow.getEntry()); - mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2.getEntry()); - mBubbleEntry3 = BubblesManager.notifToBubbleEntry(mRow3.getEntry()); + mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow); + mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2); + mBubbleEntry3 = BubblesManager.notifToBubbleEntry(mRow3); + + UserHandle handle = mock(UserHandle.class); + when(handle.getIdentifier()).thenReturn(11); + mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry( + mNotificationTestHelper.createBubble(handle)); + mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry( + mNotificationTestHelper.createBubble(handle)); // Return non-null notification data from the NEM when(mNotificationEntryManager - .getActiveNotificationUnfiltered(mRow.getEntry().getKey())).thenReturn( - mRow.getEntry()); + .getActiveNotificationUnfiltered(mRow.getKey())).thenReturn(mRow); mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); @@ -351,8 +361,8 @@ public class BubblesTest extends SysuiTestCase { verify(mNotificationEntryManager).updateNotifications(any()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); - assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); + assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); verify(mNotificationEntryManager, times(2)).updateNotifications(anyString()); assertFalse(mSysUiStateBubblesExpanded); @@ -362,20 +372,20 @@ public class BubblesTest extends SysuiTestCase { public void testPromoteBubble_autoExpand() throws Exception { mBubbleController.updateBubble(mBubbleEntry2); mBubbleController.updateBubble(mBubbleEntry); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) - .thenReturn(mRow.getEntry()); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) - .thenReturn(mRow2.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey())) + .thenReturn(mRow); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getKey())) + .thenReturn(mRow2); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); - Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getEntry().getKey()); + Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getKey()); assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b)); verify(mNotificationEntryManager, never()).performRemoveNotification( - eq(mRow.getEntry().getSbn()), any(), anyInt()); - assertThat(mRow.getEntry().isBubble()).isFalse(); + eq(mRow.getSbn()), any(), anyInt()); + assertThat(mRow.isBubble()).isFalse(); - Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey()); + Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getKey()); assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b2); mBubbleController.promoteBubbleFromOverflow(b); @@ -393,19 +403,19 @@ public class BubblesTest extends SysuiTestCase { mBubbleController.updateBubble(mBubbleEntry2); mBubbleController.updateBubble(mBubbleEntry, /* suppressFlyout */ false, /* showInShade */ true); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) - .thenReturn(mRow.getEntry()); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) - .thenReturn(mRow2.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey())) + .thenReturn(mRow); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getKey())) + .thenReturn(mRow2); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_NOTIF_CANCEL); + mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); verify(mNotificationEntryManager, times(1)).performRemoveNotification( - eq(mRow.getEntry().getSbn()), any(), anyInt()); + eq(mRow.getSbn()), any(), anyInt()); assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); - assertFalse(mRow.getEntry().isBubble()); + assertFalse(mRow.isBubble()); } @Test @@ -414,28 +424,28 @@ public class BubblesTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_CHANGED); + mRow.getKey(), Bubbles.DISMISS_USER_CHANGED); verify(mNotificationEntryManager, never()).performRemoveNotification( - eq(mRow.getEntry().getSbn()), any(), anyInt()); + eq(mRow.getSbn()), any(), anyInt()); assertFalse(mBubbleController.hasBubbles()); assertFalse(mSysUiStateBubblesExpanded); - assertTrue(mRow.getEntry().isBubble()); + assertTrue(mRow.isBubble()); } @Test public void testDismissStack() { mBubbleController.updateBubble(mBubbleEntry); verify(mNotificationEntryManager, times(1)).updateNotifications(any()); - assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); mBubbleController.updateBubble(mBubbleEntry2); verify(mNotificationEntryManager, times(2)).updateNotifications(any()); - assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey())); + assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); assertTrue(mBubbleController.hasBubbles()); mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); verify(mNotificationEntryManager, times(3)).updateNotifications(any()); - assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); - assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey())); + assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); + assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); assertFalse(mSysUiStateBubblesExpanded); } @@ -445,7 +455,7 @@ public class BubblesTest extends SysuiTestCase { assertFalse(mBubbleController.isStackExpanded()); // Mark it as a bubble and add it explicitly - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // We should have bubbles & their notifs should not be suppressed @@ -457,7 +467,7 @@ public class BubblesTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); assertTrue(mSysUiStateBubblesExpanded); @@ -467,7 +477,7 @@ public class BubblesTest extends SysuiTestCase { // Collapse mBubbleController.collapseStack(); - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertFalse(mBubbleController.isStackExpanded()); assertFalse(mSysUiStateBubblesExpanded); @@ -477,8 +487,8 @@ public class BubblesTest extends SysuiTestCase { @Ignore("Currently broken.") public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly - mEntryListener.onPendingEntryAdded(mRow.getEntry()); - mEntryListener.onPendingEntryAdded(mRow2.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); + mEntryListener.onPendingEntryAdded(mRow2); mBubbleController.updateBubble(mBubbleEntry); mBubbleController.updateBubble(mBubbleEntry2); @@ -494,30 +504,30 @@ public class BubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( - true, mRow2.getEntry().getKey()); + true, mRow2.getKey()); assertTrue(mSysUiStateBubblesExpanded); // Last added is the one that is expanded - assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); + assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry2.getKey(), mBubbleEntry2.getGroupKey())); // Switch which bubble is expanded mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( - mRow.getEntry().getKey())); + mRow.getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // collapse for previous bubble verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( - false, mRow2.getEntry().getKey()); + false, mRow2.getKey()); // expand for selected bubble verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( - true, mRow.getEntry().getKey()); + true, mRow.getKey()); // Collapse mBubbleController.collapseStack(); @@ -529,7 +539,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testExpansionRemovesShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // We should have bubbles & their notifs should not be suppressed @@ -538,12 +548,12 @@ public class BubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mTestableLooper.processAllMessages(); - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Expand mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); assertTrue(mSysUiStateBubblesExpanded); @@ -551,13 +561,13 @@ public class BubblesTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); } @Test public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // We should have bubbles & their notifs should not be suppressed @@ -566,12 +576,12 @@ public class BubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mTestableLooper.processAllMessages(); - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Expand mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); assertTrue(mSysUiStateBubblesExpanded); @@ -579,24 +589,24 @@ public class BubblesTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Send update - mEntryListener.onPreEntryUpdated(mRow.getEntry()); + mEntryListener.onPreEntryUpdated(mRow); // Nothing should have changed // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); } @Test public void testRemoveLastExpanded_selectsOverflow() { // Mark it as a bubble and add it explicitly - mEntryListener.onPendingEntryAdded(mRow.getEntry()); - mEntryListener.onPendingEntryAdded(mRow2.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); + mEntryListener.onPendingEntryAdded(mRow2); mBubbleController.updateBubble(mBubbleEntry); mBubbleController.updateBubble(mBubbleEntry2); @@ -607,10 +617,10 @@ public class BubblesTest extends SysuiTestCase { assertTrue(mSysUiStateBubblesExpanded); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + assertEquals(mRow2.getKey(), mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry2.getKey(), mBubbleEntry2.getGroupKey())); @@ -620,12 +630,12 @@ public class BubblesTest extends SysuiTestCase { mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey(), Bubbles.DISMISS_USER_GESTURE); - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); // Dismiss that one mBubbleController.removeBubble( @@ -643,7 +653,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testRemoveLastExpandedEmptyOverflow_collapses() { // Mark it as a bubble and add it explicitly - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Expand @@ -652,7 +662,7 @@ public class BubblesTest extends SysuiTestCase { assertTrue(mSysUiStateBubblesExpanded); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); // Block the bubble so it won't be in the overflow mBubbleController.removeBubble( @@ -660,10 +670,10 @@ public class BubblesTest extends SysuiTestCase { stackView.getExpandedBubble().getKey()).getKey(), Bubbles.DISMISS_BLOCKED); - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); // We should be collapsed - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertFalse(mBubbleController.hasBubbles()); assertFalse(mSysUiStateBubblesExpanded); } @@ -671,16 +681,16 @@ public class BubblesTest extends SysuiTestCase { @Test public void testAutoExpand_fails_noFlag() { assertFalse(mBubbleController.isStackExpanded()); - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); // Add the auto expand bubble - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Expansion shouldn't change verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, - mRow.getEntry().getKey()); + mRow.getKey()); assertFalse(mBubbleController.isStackExpanded()); assertFalse(mSysUiStateBubblesExpanded); @@ -688,16 +698,16 @@ public class BubblesTest extends SysuiTestCase { @Test public void testAutoExpand_succeeds_withFlag() { - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); // Add the auto expand bubble - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Expansion should change verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, - mRow.getEntry().getKey()); + mRow.getKey()); assertTrue(mBubbleController.isStackExpanded()); assertTrue(mSysUiStateBubblesExpanded); @@ -705,19 +715,19 @@ public class BubblesTest extends SysuiTestCase { @Test public void testSuppressNotif_onInitialNotif() { - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); // Add the suppress notif bubble - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Notif should be suppressed because we were foreground assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Dot + flyout is hidden because notif is suppressed - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showFlyout()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); assertFalse(mSysUiStateBubblesExpanded); } @@ -730,10 +740,10 @@ public class BubblesTest extends SysuiTestCase { assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Should show dot - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Update to suppress notif - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); mBubbleController.updateBubble(mBubbleEntry); @@ -741,22 +751,20 @@ public class BubblesTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Dot + flyout is hidden because notif is suppressed - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showFlyout()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); assertFalse(mSysUiStateBubblesExpanded); } @Test public void testExpandStackAndSelectBubble_removedFirst() { - final String key = mRow.getEntry().getKey(); - - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Simulate notification cancellation. mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL); + mRow.getKey(), mRow, REASON_APP_CANCEL); mBubbleController.expandStackAndSelectBubble(mBubbleEntry); @@ -765,12 +773,12 @@ public class BubblesTest extends SysuiTestCase { @Test public void testMarkNewNotificationAsShowInShade() { - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mTestableLooper.processAllMessages(); - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); } @Test @@ -784,7 +792,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mBubbleEntry); - mBubbleController.removeBubble(mRow.getEntry().getKey(), Bubbles.DISMISS_AGED); + mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_AGED); verify(mDeleteIntent, never()).send(); } @@ -792,7 +800,7 @@ public class BubblesTest extends SysuiTestCase { public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mBubbleEntry); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(1)).send(); } @@ -810,11 +818,11 @@ public class BubblesTest extends SysuiTestCase { mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); - mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; + mRow.getSbn().getNotification().flags &= ~FLAG_BUBBLE; NotificationListenerService.Ranking ranking = new RankingBuilder( - mRow.getEntry().getRanking()).setCanBubble(false).build(); - mRow.getEntry().setRanking(ranking); - mEntryListener.onPreEntryUpdated(mRow.getEntry()); + mRow.getRanking()).setCanBubble(false).build(); + mRow.setRanking(ranking); + mEntryListener.onPreEntryUpdated(mRow); assertFalse(mBubbleController.hasBubbles()); verify(mDeleteIntent, never()).send(); @@ -822,13 +830,13 @@ public class BubblesTest extends SysuiTestCase { @Test public void testRemoveBubble_succeeds_appCancel() { - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL); + mRow.getKey(), mRow, REASON_APP_CANCEL); // Cancels always remove so no need to intercept assertFalse(intercepted); @@ -836,19 +844,19 @@ public class BubblesTest extends SysuiTestCase { @Test public void testRemoveBubble_entryListenerRemove() { - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); // Removes the notification - mEntryListener.onEntryRemoved(mRow.getEntry(), null, false, REASON_APP_CANCEL); + mEntryListener.onEntryRemoved(mRow, null, false, REASON_APP_CANCEL); assertFalse(mBubbleController.hasBubbles()); } @Test public void removeBubble_clearAllIntercepted() { - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -856,7 +864,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL_ALL); + mRow.getKey(), mRow, REASON_CANCEL_ALL); // Intercept! assertTrue(intercepted); @@ -867,7 +875,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void removeBubble_userDismissNotifIntercepted() { - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -875,7 +883,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL); + mRow.getKey(), mRow, REASON_CANCEL); // Intercept! assertTrue(intercepted); @@ -887,7 +895,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void removeNotif_inOverflow_intercepted() { // Get bubble with notif in shade. - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -895,11 +903,11 @@ public class BubblesTest extends SysuiTestCase { // Dismiss the bubble into overflow. mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); assertFalse(mBubbleController.hasBubbles()); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL); + mRow.getKey(), mRow, REASON_CANCEL); // Notif is no longer a bubble, but still in overflow, so we intercept removal. assertTrue(intercepted); @@ -908,7 +916,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void removeNotif_notInOverflow_notIntercepted() { // Get bubble with notif in shade. - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -916,11 +924,11 @@ public class BubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE); + mRow.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE); assertFalse(mBubbleController.hasBubbles()); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL); + mRow.getKey(), mRow, REASON_CANCEL); // Notif is no longer a bubble, so we should not intercept removal. assertFalse(intercepted); @@ -934,25 +942,25 @@ public class BubblesTest extends SysuiTestCase { mBubbleEntry2, /* suppressFlyout */ false, /* showInShade */ false); mBubbleController.updateBubble( mBubbleEntry3, /* suppressFlyout */ false, /* showInShade */ false); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) - .thenReturn(mRow.getEntry()); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) - .thenReturn(mRow2.getEntry()); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getEntry().getKey())) - .thenReturn(mRow3.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey())) + .thenReturn(mRow); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getKey())) + .thenReturn(mRow2); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getKey())) + .thenReturn(mRow3); assertEquals(mBubbleData.getBubbles().size(), 3); mBubbleData.setMaxOverflowBubbles(1); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); assertEquals(mBubbleData.getBubbles().size(), 2); assertEquals(mBubbleData.getOverflowBubbles().size(), 1); mBubbleController.removeBubble( - mRow2.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mRow2.getKey(), Bubbles.DISMISS_USER_GESTURE); // Overflow max of 1 is reached; mRow is oldest, so it gets removed verify(mNotificationEntryManager, times(1)).performRemoveNotification( - eq(mRow.getEntry().getSbn()), any(), eq(REASON_CANCEL)); + eq(mRow.getSbn()), any(), eq(REASON_CANCEL)); assertEquals(mBubbleData.getBubbles().size(), 1); assertEquals(mBubbleData.getOverflowBubbles().size(), 1); } @@ -963,14 +971,14 @@ public class BubblesTest extends SysuiTestCase { mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL); + mRow.getKey(), mRow, REASON_CANCEL); // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -978,7 +986,7 @@ public class BubblesTest extends SysuiTestCase { // Should notify delegate that shade state changed verify(listener).onBubbleNotificationSuppressionChange( - mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @Test @@ -987,7 +995,7 @@ public class BubblesTest extends SysuiTestCase { mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -1001,7 +1009,7 @@ public class BubblesTest extends SysuiTestCase { // Should notify delegate that shade state changed verify(listener).onBubbleNotificationSuppressionChange( - mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @Test @@ -1094,14 +1102,47 @@ public class BubblesTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); // Overflow it - mBubbleData.dismissBubbleWithKey(mRow.getEntry().getKey(), + mBubbleData.dismissBubbleWithKey(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); - assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse(); - assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())).isTrue(); + assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse(); + assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue(); // Test mBubbleController.updateBubble(mBubbleEntry); - assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse(); + assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse(); + } + + /** + * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't + * test the loading from the repository which would be a nice thing to add. + */ + @Test + public void testOnUserChanged_overflowState() { + int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(); + int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier(); + + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + assertTrue(mBubbleController.hasBubbles()); + mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); + + // Verify these are in the overflow + assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey())).isNotNull(); + assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull(); + + // Switch users + mBubbleController.onUserChanged(secondUserId); + assertThat(mBubbleData.getOverflowBubbles().isEmpty()); + + // Give this user some bubbles + mBubbleController.updateBubble(mBubbleEntryUser11); + mBubbleController.updateBubble(mBubbleEntry2User11); + assertTrue(mBubbleController.hasBubbles()); + mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); + + // Verify these are in the overflow + assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull(); + assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull(); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 1ba7f8aa5e24b..89825d240ec1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -158,8 +158,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; private NotifCollectionListener mEntryListener; private NotificationTestHelper mNotificationTestHelper; - private ExpandableNotificationRow mRow; - private ExpandableNotificationRow mRow2; + private NotificationEntry mRow; + private NotificationEntry mRow2; private ExpandableNotificationRow mNonBubbleNotifRow; private BubbleEntry mBubbleEntry; private BubbleEntry mBubbleEntry2; @@ -224,8 +224,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); - mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow.getEntry()); - mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2.getEntry()); + mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow); + mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2); mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); @@ -306,19 +306,19 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testRemoveBubble() { mBubbleController.updateBubble(mBubbleEntry); - assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); assertTrue(mBubbleController.hasBubbles()); verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); - assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); + assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); } @Test public void testRemoveBubble_withDismissedNotif_inOverflow() { - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -326,40 +326,40 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Make it look like dismissed notif - mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true); + mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true); // Now remove the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); - assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); + assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())); // We don't remove the notification since the bubble is still in overflow. - verify(mNotifCallback, never()).removeNotification(eq(mRow.getEntry()), any(), anyInt()); + verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt()); assertFalse(mBubbleController.hasBubbles()); } @Test public void testRemoveBubble_withDismissedNotif_notInOverflow() { - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); - when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) - .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey())) + .thenReturn(mRow); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Make it look like dismissed notif - mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true); + mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true); // Now remove the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_NOTIF_CANCEL); - assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())); + mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); + assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())); // Since the notif is dismissed and not in overflow, once the bubble is removed, // removeNotification gets called to really remove the notif - verify(mNotifCallback, times(1)).removeNotification(eq(mRow.getEntry()), + verify(mNotifCallback, times(1)).removeNotification(eq(mRow), any(), anyInt()); assertFalse(mBubbleController.hasBubbles()); } @@ -368,16 +368,16 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { public void testDismissStack() { mBubbleController.updateBubble(mBubbleEntry); verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); - assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); mBubbleController.updateBubble(mBubbleEntry2); verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); - assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey())); + assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); assertTrue(mBubbleController.hasBubbles()); mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); verify(mNotifCallback, times(3)).invalidateNotifications(anyString()); - assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); - assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey())); + assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); + assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); } @Test @@ -385,7 +385,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertFalse(mBubbleController.isStackExpanded()); // Mark it as a bubble and add it explicitly - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // We should have bubbles & their notifs should not be suppressed @@ -397,7 +397,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); // Make sure the notif is suppressed assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -405,7 +405,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { // Collapse mBubbleController.collapseStack(); - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertFalse(mBubbleController.isStackExpanded()); } @@ -413,8 +413,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Ignore("Currently broken.") public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly - mEntryListener.onEntryAdded(mRow.getEntry()); - mEntryListener.onEntryAdded(mRow2.getEntry()); + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); mBubbleController.updateBubble(mBubbleEntry); mBubbleController.updateBubble(mBubbleEntry2); @@ -430,28 +430,28 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( - true, mRow2.getEntry().getKey()); + true, mRow2.getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); + assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry2.getKey(), mBubbleEntry2.getGroupKey())); // Switch which bubble is expanded mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( - mRow.getEntry().getKey())); + mRow.getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // collapse for previous bubble verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( - false, mRow2.getEntry().getKey()); + false, mRow2.getKey()); // expand for selected bubble verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( - true, mRow.getEntry().getKey()); + true, mRow.getKey()); // Collapse @@ -462,7 +462,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testExpansionRemovesShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // We should have bubbles & their notifs should not be suppressed @@ -471,24 +471,24 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mTestableLooper.processAllMessages(); - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Expand mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); } @Test public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // We should have bubbles & their notifs should not be suppressed @@ -497,35 +497,35 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mTestableLooper.processAllMessages(); - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Expand mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Send update - mEntryListener.onEntryUpdated(mRow.getEntry()); + mEntryListener.onEntryUpdated(mRow); // Nothing should have changed // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Notif shouldn't show dot after expansion - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); } @Test public void testRemoveLastExpanded_selectsOverflow() { // Mark it as a bubble and add it explicitly - mEntryListener.onEntryAdded(mRow.getEntry()); - mEntryListener.onEntryAdded(mRow2.getEntry()); + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); mBubbleController.updateBubble(mBubbleEntry); mBubbleController.updateBubble(mBubbleEntry2); @@ -534,10 +534,10 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + assertEquals(mRow2.getKey(), mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry2.getKey(), mBubbleEntry2.getGroupKey())); @@ -547,12 +547,12 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey(), Bubbles.DISMISS_USER_GESTURE); - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( stackView.getExpandedBubble().getKey()).getKey()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); // Dismiss that one mBubbleController.removeBubble( @@ -569,7 +569,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testRemoveLastExpandedEmptyOverflow_collapses() { // Mark it as a bubble and add it explicitly - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Expand @@ -577,7 +577,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); // Block the bubble so it won't be in the overflow mBubbleController.removeBubble( @@ -585,10 +585,10 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { stackView.getExpandedBubble().getKey()).getKey(), Bubbles.DISMISS_BLOCKED); - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); // We should be collapsed - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertFalse(mBubbleController.hasBubbles()); } @@ -596,49 +596,49 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testAutoExpand_fails_noFlag() { assertFalse(mBubbleController.isStackExpanded()); - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); // Add the auto expand bubble - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Expansion shouldn't change verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, - mRow.getEntry().getKey()); + mRow.getKey()); assertFalse(mBubbleController.isStackExpanded()); } @Test public void testAutoExpand_succeeds_withFlag() { - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); // Add the auto expand bubble - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Expansion should change verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, - mRow.getEntry().getKey()); + mRow.getKey()); assertTrue(mBubbleController.isStackExpanded()); } @Test public void testSuppressNotif_onInitialNotif() { - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); // Add the suppress notif bubble - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); // Notif should be suppressed because we were foreground assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Dot + flyout is hidden because notif is suppressed - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showFlyout()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); } @Test @@ -649,10 +649,10 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Should show dot - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Update to suppress notif - setMetadataFlags(mRow.getEntry(), + setMetadataFlags(mRow, Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); mBubbleController.updateBubble(mBubbleEntry); @@ -660,18 +660,18 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Dot + flyout is hidden because notif is suppressed - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); - assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showFlyout()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); + assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); } @Test public void testMarkNewNotificationAsShowInShade() { - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); mTestableLooper.processAllMessages(); - assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot()); + assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); } @Test @@ -685,7 +685,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mBubbleEntry); - mBubbleController.removeBubble(mRow.getEntry().getKey(), Bubbles.DISMISS_AGED); + mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_AGED); verify(mDeleteIntent, never()).send(); } @@ -693,7 +693,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mBubbleEntry); mBubbleController.removeBubble( - mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(1)).send(); } @@ -711,11 +711,11 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); - mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; + mRow.getSbn().getNotification().flags &= ~FLAG_BUBBLE; NotificationListenerService.Ranking ranking = new RankingBuilder( - mRow.getEntry().getRanking()).setCanBubble(false).build(); - mRow.getEntry().setRanking(ranking); - mEntryListener.onEntryUpdated(mRow.getEntry()); + mRow.getRanking()).setCanBubble(false).build(); + mRow.setRanking(ranking); + mEntryListener.onEntryUpdated(mRow); assertFalse(mBubbleController.hasBubbles()); verify(mDeleteIntent, never()).send(); @@ -723,26 +723,26 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testRemoveBubble_entryListenerRemove() { - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); // Removes the notification - mEntryListener.onEntryRemoved(mRow.getEntry(), 0); + mEntryListener.onEntryRemoved(mRow, 0); assertFalse(mBubbleController.hasBubbles()); } @Test public void removeBubble_intercepted() { - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); - boolean intercepted = mBubblesManager.handleDismissalInterception(mRow.getEntry()); + boolean intercepted = mBubblesManager.handleDismissalInterception(mRow); // Intercept! assertTrue(intercepted); @@ -753,7 +753,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void removeBubble_dismissIntoOverflow_intercepted() { - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -761,11 +761,11 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Dismiss the bubble - mBubbleController.removeBubble(mRow.getEntry().getKey(), Bubbles.DISMISS_USER_GESTURE); + mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); assertFalse(mBubbleController.hasBubbles()); // Dismiss the notification - boolean intercepted = mBubblesManager.handleDismissalInterception(mRow.getEntry()); + boolean intercepted = mBubblesManager.handleDismissalInterception(mRow); // Intercept dismissal since bubble is going into overflow assertTrue(intercepted); @@ -773,7 +773,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void removeBubble_notIntercepted() { - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -781,11 +781,11 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); // Dismiss the bubble - mBubbleController.removeBubble(mRow.getEntry().getKey(), Bubbles.DISMISS_NOTIF_CANCEL); + mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); assertFalse(mBubbleController.hasBubbles()); // Dismiss the notification - boolean intercepted = mBubblesManager.handleDismissalInterception(mRow.getEntry()); + boolean intercepted = mBubblesManager.handleDismissalInterception(mRow); // Not a bubble anymore so we don't intercept dismissal. assertFalse(intercepted); @@ -797,13 +797,13 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mBubbleEntry.getKey(), mBubbleEntry.getGroupKey())); - mBubblesManager.handleDismissalInterception(mRow.getEntry()); + mBubblesManager.handleDismissalInterception(mRow); // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -811,7 +811,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { // Should notify delegate that shade state changed verify(listener).onBubbleNotificationSuppressionChange( - mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @Test @@ -820,7 +820,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -834,7 +834,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { // Should notify delegate that shade state changed verify(listener).onBubbleNotificationSuppressionChange( - mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @Test