Loading packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +7 −0 Original line number Original line Diff line number Diff line Loading @@ -102,6 +102,13 @@ class Bubble implements BubbleViewProvider { return user.getIdentifier() + "|" + entry.getSbn().getPackageName(); return user.getIdentifier() + "|" + entry.getSbn().getPackageName(); } } // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble Bubble(ShortcutInfo shortcutInfo) { mShortcutInfo = shortcutInfo; mKey = shortcutInfo.getId(); mGroupId = shortcutInfo.getId(); } /** Used in tests when no UI is required. */ /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) @VisibleForTesting(visibility = PRIVATE) Bubble(NotificationEntry e, Bubble(NotificationEntry e, Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +63 −10 Original line number Original line Diff line number Diff line Loading @@ -15,24 +15,32 @@ */ */ package com.android.systemui.bubbles package com.android.systemui.bubbles import android.annotation.SuppressLint import android.annotation.UserIdInt import android.annotation.UserIdInt import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED import android.os.UserHandle import android.util.Log import android.util.Log import com.android.systemui.bubbles.storage.BubbleEntity import com.android.systemui.bubbles.storage.BubblePersistentRepository import com.android.systemui.bubbles.storage.BubblePersistentRepository import com.android.systemui.bubbles.storage.BubbleVolatileRepository import com.android.systemui.bubbles.storage.BubbleVolatileRepository import com.android.systemui.bubbles.storage.BubbleXmlEntity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.launch import kotlinx.coroutines.yield import kotlinx.coroutines.yield import javax.inject.Inject import javax.inject.Inject import javax.inject.Singleton import javax.inject.Singleton @Singleton @Singleton internal class BubbleDataRepository @Inject constructor( internal class BubbleDataRepository @Inject constructor( private val volatileRepository: BubbleVolatileRepository, private val volatileRepository: BubbleVolatileRepository, private val persistentRepository: BubblePersistentRepository private val persistentRepository: BubblePersistentRepository, private val launcherApps: LauncherApps ) { ) { private val ioScope = CoroutineScope(Dispatchers.IO) private val ioScope = CoroutineScope(Dispatchers.IO) Loading Loading @@ -64,10 +72,10 @@ internal class BubbleDataRepository @Inject constructor( if (entities.isNotEmpty()) persistToDisk() if (entities.isNotEmpty()) persistToDisk() } } private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleXmlEntity> { private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> return bubbles.mapNotNull { b -> val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null BubbleXmlEntity(userId, b.packageName, shortcutId) BubbleEntity(userId, b.packageName, shortcutId) } } } } Loading Loading @@ -100,15 +108,60 @@ internal class BubbleDataRepository @Inject constructor( /** /** * Load bubbles from disk. * Load bubbles from disk. */ */ // TODO: call this method from BubbleController and update UI @SuppressLint("WrongConstant") fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { val bubbleXmlEntities = persistentRepository.readFromDisk() /** volatileRepository.addBubbles(bubbleXmlEntities) * Load BubbleEntity from disk. uiScope.launch { * e.g. // TODO: transform bubbleXmlEntities into bubbles * [ // cb(bubbles) * BubbleEntity(0, "com.example.messenger", "id-2"), } * BubbleEntity(10, "com.example.chat", "my-id1") * BubbleEntity(0, "com.example.messenger", "id-1") * ] */ val entities = persistentRepository.readFromDisk() volatileRepository.addBubbles(entities) /** * Extract userId/packageName from these entities. * e.g. * [ * ShortcutKey(0, "com.example.messenger"), ShortcutKey(0, "com.example.chat") * ] */ val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet() /** * Retrieve shortcuts with given userId/packageName combination, then construct a mapping * between BubbleEntity and ShortcutInfo. * e.g. * { * BubbleEntity(0, "com.example.messenger", "id-0") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-0"), * BubbleEntity(0, "com.example.messenger", "id-2") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2"), * BubbleEntity(10, "com.example.chat", "id-1") -> * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-1"), * BubbleEntity(10, "com.example.chat", "id-3") -> * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-3") * } */ val shortcutMap = shortcutKeys.flatMap { key -> launcherApps.getShortcuts( LauncherApps.ShortcutQuery() .setPackage(key.pkg) .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId)) ?.map { BubbleEntity(key.userId, key.pkg, it.id) to it } ?: emptyList() }.toMap() // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them // into Bubble. val bubbles = entities.mapNotNull { entity -> shortcutMap[entity]?.let { Bubble(it) } } uiScope.launch { cb(bubbles) } } } private 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 = FLAG_MATCH_DYNAMIC or FLAG_MATCH_PINNED_BY_ANY_LAUNCHER or FLAG_MATCH_CACHED No newline at end of file packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlEntity.kt→packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -17,7 +17,7 @@ package com.android.systemui.bubbles.storage import android.annotation.UserIdInt import android.annotation.UserIdInt data class BubbleXmlEntity( data class BubbleEntity( @UserIdInt val userId: Int, @UserIdInt val userId: Int, val packageName: String, val packageName: String, val shortcutId: String val shortcutId: String Loading packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt +2 −2 Original line number Original line Diff line number Diff line Loading @@ -32,7 +32,7 @@ class BubblePersistentRepository @Inject constructor( private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir, private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir, "overflow_bubbles.xml"), "overflow-bubbles") "overflow_bubbles.xml"), "overflow-bubbles") fun persistsToDisk(bubbles: List<BubbleXmlEntity>): Boolean { fun persistsToDisk(bubbles: List<BubbleEntity>): Boolean { if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles") if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles") synchronized(bubbleFile) { synchronized(bubbleFile) { val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) { val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) { Loading @@ -52,7 +52,7 @@ class BubblePersistentRepository @Inject constructor( return false return false } } fun readFromDisk(): List<BubbleXmlEntity> { fun readFromDisk(): List<BubbleEntity> { synchronized(bubbleFile) { synchronized(bubbleFile) { try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) { try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) { Log.e(TAG, "Failed to open bubble file", e) Log.e(TAG, "Failed to open bubble file", e) Loading packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt +4 −4 Original line number Original line Diff line number Diff line Loading @@ -29,12 +29,12 @@ class BubbleVolatileRepository @Inject constructor() { /** /** * An ordered set of bubbles based on their natural ordering. * An ordered set of bubbles based on their natural ordering. */ */ private val entities = mutableSetOf<BubbleXmlEntity>() private val entities = mutableSetOf<BubbleEntity>() /** /** * Returns a snapshot of all the bubbles. * Returns a snapshot of all the bubbles. */ */ val bubbles: List<BubbleXmlEntity> val bubbles: List<BubbleEntity> @Synchronized @Synchronized get() = entities.toList() get() = entities.toList() Loading @@ -43,7 +43,7 @@ class BubbleVolatileRepository @Inject constructor() { * it will be moved to the last. * it will be moved to the last. */ */ @Synchronized @Synchronized fun addBubbles(bubbles: List<BubbleXmlEntity>) { fun addBubbles(bubbles: List<BubbleEntity>) { if (bubbles.isEmpty()) return if (bubbles.isEmpty()) return bubbles.forEach { entities.remove(it) } bubbles.forEach { entities.remove(it) } if (entities.size + bubbles.size >= CAPACITY) { if (entities.size + bubbles.size >= CAPACITY) { Loading @@ -53,7 +53,7 @@ class BubbleVolatileRepository @Inject constructor() { } } @Synchronized @Synchronized fun removeBubbles(bubbles: List<BubbleXmlEntity>) { fun removeBubbles(bubbles: List<BubbleEntity>) { bubbles.forEach { entities.remove(it) } bubbles.forEach { entities.remove(it) } } } } } Loading
packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +7 −0 Original line number Original line Diff line number Diff line Loading @@ -102,6 +102,13 @@ class Bubble implements BubbleViewProvider { return user.getIdentifier() + "|" + entry.getSbn().getPackageName(); return user.getIdentifier() + "|" + entry.getSbn().getPackageName(); } } // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble Bubble(ShortcutInfo shortcutInfo) { mShortcutInfo = shortcutInfo; mKey = shortcutInfo.getId(); mGroupId = shortcutInfo.getId(); } /** Used in tests when no UI is required. */ /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) @VisibleForTesting(visibility = PRIVATE) Bubble(NotificationEntry e, Bubble(NotificationEntry e, Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +63 −10 Original line number Original line Diff line number Diff line Loading @@ -15,24 +15,32 @@ */ */ package com.android.systemui.bubbles package com.android.systemui.bubbles import android.annotation.SuppressLint import android.annotation.UserIdInt import android.annotation.UserIdInt import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED import android.os.UserHandle import android.util.Log import android.util.Log import com.android.systemui.bubbles.storage.BubbleEntity import com.android.systemui.bubbles.storage.BubblePersistentRepository import com.android.systemui.bubbles.storage.BubblePersistentRepository import com.android.systemui.bubbles.storage.BubbleVolatileRepository import com.android.systemui.bubbles.storage.BubbleVolatileRepository import com.android.systemui.bubbles.storage.BubbleXmlEntity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.launch import kotlinx.coroutines.yield import kotlinx.coroutines.yield import javax.inject.Inject import javax.inject.Inject import javax.inject.Singleton import javax.inject.Singleton @Singleton @Singleton internal class BubbleDataRepository @Inject constructor( internal class BubbleDataRepository @Inject constructor( private val volatileRepository: BubbleVolatileRepository, private val volatileRepository: BubbleVolatileRepository, private val persistentRepository: BubblePersistentRepository private val persistentRepository: BubblePersistentRepository, private val launcherApps: LauncherApps ) { ) { private val ioScope = CoroutineScope(Dispatchers.IO) private val ioScope = CoroutineScope(Dispatchers.IO) Loading Loading @@ -64,10 +72,10 @@ internal class BubbleDataRepository @Inject constructor( if (entities.isNotEmpty()) persistToDisk() if (entities.isNotEmpty()) persistToDisk() } } private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleXmlEntity> { private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> return bubbles.mapNotNull { b -> val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null BubbleXmlEntity(userId, b.packageName, shortcutId) BubbleEntity(userId, b.packageName, shortcutId) } } } } Loading Loading @@ -100,15 +108,60 @@ internal class BubbleDataRepository @Inject constructor( /** /** * Load bubbles from disk. * Load bubbles from disk. */ */ // TODO: call this method from BubbleController and update UI @SuppressLint("WrongConstant") fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { val bubbleXmlEntities = persistentRepository.readFromDisk() /** volatileRepository.addBubbles(bubbleXmlEntities) * Load BubbleEntity from disk. uiScope.launch { * e.g. // TODO: transform bubbleXmlEntities into bubbles * [ // cb(bubbles) * BubbleEntity(0, "com.example.messenger", "id-2"), } * BubbleEntity(10, "com.example.chat", "my-id1") * BubbleEntity(0, "com.example.messenger", "id-1") * ] */ val entities = persistentRepository.readFromDisk() volatileRepository.addBubbles(entities) /** * Extract userId/packageName from these entities. * e.g. * [ * ShortcutKey(0, "com.example.messenger"), ShortcutKey(0, "com.example.chat") * ] */ val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet() /** * Retrieve shortcuts with given userId/packageName combination, then construct a mapping * between BubbleEntity and ShortcutInfo. * e.g. * { * BubbleEntity(0, "com.example.messenger", "id-0") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-0"), * BubbleEntity(0, "com.example.messenger", "id-2") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2"), * BubbleEntity(10, "com.example.chat", "id-1") -> * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-1"), * BubbleEntity(10, "com.example.chat", "id-3") -> * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-3") * } */ val shortcutMap = shortcutKeys.flatMap { key -> launcherApps.getShortcuts( LauncherApps.ShortcutQuery() .setPackage(key.pkg) .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId)) ?.map { BubbleEntity(key.userId, key.pkg, it.id) to it } ?: emptyList() }.toMap() // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them // into Bubble. val bubbles = entities.mapNotNull { entity -> shortcutMap[entity]?.let { Bubble(it) } } uiScope.launch { cb(bubbles) } } } private 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 = FLAG_MATCH_DYNAMIC or FLAG_MATCH_PINNED_BY_ANY_LAUNCHER or FLAG_MATCH_CACHED No newline at end of file
packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlEntity.kt→packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -17,7 +17,7 @@ package com.android.systemui.bubbles.storage import android.annotation.UserIdInt import android.annotation.UserIdInt data class BubbleXmlEntity( data class BubbleEntity( @UserIdInt val userId: Int, @UserIdInt val userId: Int, val packageName: String, val packageName: String, val shortcutId: String val shortcutId: String Loading
packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt +2 −2 Original line number Original line Diff line number Diff line Loading @@ -32,7 +32,7 @@ class BubblePersistentRepository @Inject constructor( private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir, private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir, "overflow_bubbles.xml"), "overflow-bubbles") "overflow_bubbles.xml"), "overflow-bubbles") fun persistsToDisk(bubbles: List<BubbleXmlEntity>): Boolean { fun persistsToDisk(bubbles: List<BubbleEntity>): Boolean { if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles") if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles") synchronized(bubbleFile) { synchronized(bubbleFile) { val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) { val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) { Loading @@ -52,7 +52,7 @@ class BubblePersistentRepository @Inject constructor( return false return false } } fun readFromDisk(): List<BubbleXmlEntity> { fun readFromDisk(): List<BubbleEntity> { synchronized(bubbleFile) { synchronized(bubbleFile) { try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) { try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) { Log.e(TAG, "Failed to open bubble file", e) Log.e(TAG, "Failed to open bubble file", e) Loading
packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt +4 −4 Original line number Original line Diff line number Diff line Loading @@ -29,12 +29,12 @@ class BubbleVolatileRepository @Inject constructor() { /** /** * An ordered set of bubbles based on their natural ordering. * An ordered set of bubbles based on their natural ordering. */ */ private val entities = mutableSetOf<BubbleXmlEntity>() private val entities = mutableSetOf<BubbleEntity>() /** /** * Returns a snapshot of all the bubbles. * Returns a snapshot of all the bubbles. */ */ val bubbles: List<BubbleXmlEntity> val bubbles: List<BubbleEntity> @Synchronized @Synchronized get() = entities.toList() get() = entities.toList() Loading @@ -43,7 +43,7 @@ class BubbleVolatileRepository @Inject constructor() { * it will be moved to the last. * it will be moved to the last. */ */ @Synchronized @Synchronized fun addBubbles(bubbles: List<BubbleXmlEntity>) { fun addBubbles(bubbles: List<BubbleEntity>) { if (bubbles.isEmpty()) return if (bubbles.isEmpty()) return bubbles.forEach { entities.remove(it) } bubbles.forEach { entities.remove(it) } if (entities.size + bubbles.size >= CAPACITY) { if (entities.size + bubbles.size >= CAPACITY) { Loading @@ -53,7 +53,7 @@ class BubbleVolatileRepository @Inject constructor() { } } @Synchronized @Synchronized fun removeBubbles(bubbles: List<BubbleXmlEntity>) { fun removeBubbles(bubbles: List<BubbleEntity>) { bubbles.forEach { entities.remove(it) } bubbles.forEach { entities.remove(it) } } } } }