Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +2 −2 Original line number Original line Diff line number Diff line Loading @@ -162,10 +162,10 @@ internal class BubbleDataRepository @Inject constructor( } } uiScope.launch { cb(bubbles) } uiScope.launch { cb(bubbles) } } } private data class ShortcutKey(val userId: Int, val pkg: String) } } data class ShortcutKey(val userId: Int, val pkg: String) private const val TAG = "BubbleDataRepository" private const val TAG = "BubbleDataRepository" private const val DEBUG = false private const val DEBUG = false private const val SHORTCUT_QUERY_FLAG = private const val SHORTCUT_QUERY_FLAG = Loading packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt +39 −8 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,10 @@ */ */ package com.android.systemui.bubbles.storage package com.android.systemui.bubbles.storage import android.content.pm.LauncherApps import android.os.UserHandle import com.android.internal.annotations.VisibleForTesting import com.android.systemui.bubbles.ShortcutKey import javax.inject.Inject import javax.inject.Inject import javax.inject.Singleton import javax.inject.Singleton Loading @@ -25,11 +29,19 @@ private const val CAPACITY = 16 * manipulation. * manipulation. */ */ @Singleton @Singleton class BubbleVolatileRepository @Inject constructor() { class BubbleVolatileRepository @Inject constructor( private val launcherApps: LauncherApps ) { /** /** * An ordered set of bubbles based on their natural ordering. * An ordered set of bubbles based on their natural ordering. */ */ private val entities = mutableSetOf<BubbleEntity>() private var entities = mutableSetOf<BubbleEntity>() /** * The capacity of the cache. */ @VisibleForTesting var capacity = CAPACITY /** /** * Returns a snapshot of all the bubbles. * Returns a snapshot of all the bubbles. Loading @@ -45,15 +57,34 @@ class BubbleVolatileRepository @Inject constructor() { @Synchronized @Synchronized fun addBubbles(bubbles: List<BubbleEntity>) { fun addBubbles(bubbles: List<BubbleEntity>) { if (bubbles.isEmpty()) return if (bubbles.isEmpty()) return bubbles.forEach { entities.remove(it) } // Verify the size of given bubbles is within capacity, otherwise trim down to capacity if (entities.size + bubbles.size >= CAPACITY) { val bubblesInRange = bubbles.takeLast(capacity) entities.drop(entities.size + bubbles.size - CAPACITY) // To ensure natural ordering of the bubbles, removes bubbles which already exist val uniqueBubbles = bubblesInRange.filterNot { entities.remove(it) } val overflowCount = entities.size + bubblesInRange.size - capacity if (overflowCount > 0) { // Uncache ShortcutInfo of bubbles that will be removed due to capacity uncache(entities.take(overflowCount)) entities = entities.drop(overflowCount).toMutableSet() } } entities.addAll(bubbles) entities.addAll(bubblesInRange) cache(uniqueBubbles) } } @Synchronized @Synchronized fun removeBubbles(bubbles: List<BubbleEntity>) { fun removeBubbles(bubbles: List<BubbleEntity>) = uncache(bubbles.filter { entities.remove(it) }) bubbles.forEach { entities.remove(it) } private fun cache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) } } private fun uncache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> launcherApps.uncacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) } } } } } packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +62 −7 Original line number Original line Diff line number Diff line Loading @@ -16,37 +16,92 @@ package com.android.systemui.bubbles.storage package com.android.systemui.bubbles.storage import android.content.pm.LauncherApps import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq import junit.framework.Assert.assertEquals import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Before import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest @SmallTest @RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class) class BubbleVolatileRepositoryTest : SysuiTestCase() { class BubbleVolatileRepositoryTest : SysuiTestCase() { private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1") private val user0 = UserHandle.of(0) private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", "k2") private val user10 = UserHandle.of(10) private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3") private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1") private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2") private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3") private val bubbles = listOf(bubble1, bubble2, bubble3) private val bubbles = listOf(bubble1, bubble2, bubble3) private lateinit var repository: BubbleVolatileRepository private lateinit var repository: BubbleVolatileRepository private lateinit var launcherApps: LauncherApps @Before @Before fun setup() { fun setup() { repository = BubbleVolatileRepository() launcherApps = mock(LauncherApps::class.java) repository = BubbleVolatileRepository(launcherApps) } } @Test @Test fun testAddAndRemoveBubbles() { fun testAddBubbles() { repository.addBubbles(bubbles) repository.addBubbles(bubbles) assertEquals(bubbles, repository.bubbles) assertEquals(bubbles, repository.bubbles) 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(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) repository.addBubbles(listOf(bubble1)) repository.addBubbles(listOf(bubble1)) assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles) assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles) verifyNoMoreInteractions(launcherApps) } @Test fun testRemoveBubbles() { repository.addBubbles(bubbles) assertEquals(bubbles, repository.bubbles) repository.removeBubbles(listOf(bubble3)) repository.removeBubbles(listOf(bubble3)) assertEquals(listOf(bubble2, bubble1), repository.bubbles) assertEquals(listOf(bubble1, bubble2), repository.bubbles) verify(launcherApps).uncacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } @Test fun testAddAndRemoveBubblesWhenExceedingCapacity() { repository.capacity = 2 // push bubbles beyond capacity repository.addBubbles(bubbles) // verify it is trim down to capacity assertEquals(listOf(bubble2, bubble3), repository.bubbles) 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(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) repository.addBubbles(listOf(bubble1)) // verify the oldest bubble is popped assertEquals(listOf(bubble3, bubble1), repository.bubbles) verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT), eq(listOf("alice and bob")), eq(user10), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } } } } private const val PKG_MESSENGER = "com.example.messenger" private const val PKG_CHAT = "com.example.chat" Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +2 −2 Original line number Original line Diff line number Diff line Loading @@ -162,10 +162,10 @@ internal class BubbleDataRepository @Inject constructor( } } uiScope.launch { cb(bubbles) } uiScope.launch { cb(bubbles) } } } private data class ShortcutKey(val userId: Int, val pkg: String) } } data class ShortcutKey(val userId: Int, val pkg: String) private const val TAG = "BubbleDataRepository" private const val TAG = "BubbleDataRepository" private const val DEBUG = false private const val DEBUG = false private const val SHORTCUT_QUERY_FLAG = private const val SHORTCUT_QUERY_FLAG = Loading
packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt +39 −8 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,10 @@ */ */ package com.android.systemui.bubbles.storage package com.android.systemui.bubbles.storage import android.content.pm.LauncherApps import android.os.UserHandle import com.android.internal.annotations.VisibleForTesting import com.android.systemui.bubbles.ShortcutKey import javax.inject.Inject import javax.inject.Inject import javax.inject.Singleton import javax.inject.Singleton Loading @@ -25,11 +29,19 @@ private const val CAPACITY = 16 * manipulation. * manipulation. */ */ @Singleton @Singleton class BubbleVolatileRepository @Inject constructor() { class BubbleVolatileRepository @Inject constructor( private val launcherApps: LauncherApps ) { /** /** * An ordered set of bubbles based on their natural ordering. * An ordered set of bubbles based on their natural ordering. */ */ private val entities = mutableSetOf<BubbleEntity>() private var entities = mutableSetOf<BubbleEntity>() /** * The capacity of the cache. */ @VisibleForTesting var capacity = CAPACITY /** /** * Returns a snapshot of all the bubbles. * Returns a snapshot of all the bubbles. Loading @@ -45,15 +57,34 @@ class BubbleVolatileRepository @Inject constructor() { @Synchronized @Synchronized fun addBubbles(bubbles: List<BubbleEntity>) { fun addBubbles(bubbles: List<BubbleEntity>) { if (bubbles.isEmpty()) return if (bubbles.isEmpty()) return bubbles.forEach { entities.remove(it) } // Verify the size of given bubbles is within capacity, otherwise trim down to capacity if (entities.size + bubbles.size >= CAPACITY) { val bubblesInRange = bubbles.takeLast(capacity) entities.drop(entities.size + bubbles.size - CAPACITY) // To ensure natural ordering of the bubbles, removes bubbles which already exist val uniqueBubbles = bubblesInRange.filterNot { entities.remove(it) } val overflowCount = entities.size + bubblesInRange.size - capacity if (overflowCount > 0) { // Uncache ShortcutInfo of bubbles that will be removed due to capacity uncache(entities.take(overflowCount)) entities = entities.drop(overflowCount).toMutableSet() } } entities.addAll(bubbles) entities.addAll(bubblesInRange) cache(uniqueBubbles) } } @Synchronized @Synchronized fun removeBubbles(bubbles: List<BubbleEntity>) { fun removeBubbles(bubbles: List<BubbleEntity>) = uncache(bubbles.filter { entities.remove(it) }) bubbles.forEach { entities.remove(it) } private fun cache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) } } private fun uncache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> launcherApps.uncacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) } } } } }
packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +62 −7 Original line number Original line Diff line number Diff line Loading @@ -16,37 +16,92 @@ package com.android.systemui.bubbles.storage package com.android.systemui.bubbles.storage import android.content.pm.LauncherApps import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq import junit.framework.Assert.assertEquals import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Before import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest @SmallTest @RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class) class BubbleVolatileRepositoryTest : SysuiTestCase() { class BubbleVolatileRepositoryTest : SysuiTestCase() { private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1") private val user0 = UserHandle.of(0) private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", "k2") private val user10 = UserHandle.of(10) private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3") private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1") private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2") private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3") private val bubbles = listOf(bubble1, bubble2, bubble3) private val bubbles = listOf(bubble1, bubble2, bubble3) private lateinit var repository: BubbleVolatileRepository private lateinit var repository: BubbleVolatileRepository private lateinit var launcherApps: LauncherApps @Before @Before fun setup() { fun setup() { repository = BubbleVolatileRepository() launcherApps = mock(LauncherApps::class.java) repository = BubbleVolatileRepository(launcherApps) } } @Test @Test fun testAddAndRemoveBubbles() { fun testAddBubbles() { repository.addBubbles(bubbles) repository.addBubbles(bubbles) assertEquals(bubbles, repository.bubbles) assertEquals(bubbles, repository.bubbles) 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(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) repository.addBubbles(listOf(bubble1)) repository.addBubbles(listOf(bubble1)) assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles) assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles) verifyNoMoreInteractions(launcherApps) } @Test fun testRemoveBubbles() { repository.addBubbles(bubbles) assertEquals(bubbles, repository.bubbles) repository.removeBubbles(listOf(bubble3)) repository.removeBubbles(listOf(bubble3)) assertEquals(listOf(bubble2, bubble1), repository.bubbles) assertEquals(listOf(bubble1, bubble2), repository.bubbles) verify(launcherApps).uncacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } @Test fun testAddAndRemoveBubblesWhenExceedingCapacity() { repository.capacity = 2 // push bubbles beyond capacity repository.addBubbles(bubbles) // verify it is trim down to capacity assertEquals(listOf(bubble2, bubble3), repository.bubbles) 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(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) repository.addBubbles(listOf(bubble1)) // verify the oldest bubble is popped assertEquals(listOf(bubble3, bubble1), repository.bubbles) verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT), eq(listOf("alice and bob")), eq(user10), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } } } } private const val PKG_MESSENGER = "com.example.messenger" private const val PKG_CHAT = "com.example.chat"