Merge "Persists bubbles to disk (part 3)" into rvc-dev am: 8e65e07631

Change-Id: I97a6606d5d7afe0ef428b613d9533300275f2be5
This commit is contained in:
TreeHugger Robot
2020-05-13 00:49:44 +00:00
committed by Automerger Merge Worker
6 changed files with 83 additions and 23 deletions

View File

@@ -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,

View File

@@ -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)
@@ -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)
} }
} }
@@ -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

View File

@@ -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

View File

@@ -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) {
@@ -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)

View File

@@ -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()
@@ -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) {
@@ -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) }
} }
} }

View File

@@ -35,7 +35,7 @@ private const val ATTR_SHORTCUT_ID = "sid"
* Writes the bubbles in xml format into given output stream. * Writes the bubbles in xml format into given output stream.
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun writeXml(stream: OutputStream, bubbles: List<BubbleXmlEntity>) { fun writeXml(stream: OutputStream, bubbles: List<BubbleEntity>) {
val serializer: XmlSerializer = FastXmlSerializer() val serializer: XmlSerializer = FastXmlSerializer()
serializer.setOutput(stream, StandardCharsets.UTF_8.name()) serializer.setOutput(stream, StandardCharsets.UTF_8.name())
serializer.startDocument(null, true) serializer.startDocument(null, true)
@@ -51,7 +51,7 @@ fun writeXml(stream: OutputStream, bubbles: List<BubbleXmlEntity>) {
* <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" /> * <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" />
* ``` * ```
*/ */
private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleXmlEntity) { private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
try { try {
serializer.startTag(null, TAG_BUBBLE) serializer.startTag(null, TAG_BUBBLE)
serializer.attribute(null, ATTR_USER_ID, bubble.userId.toString()) serializer.attribute(null, ATTR_USER_ID, bubble.userId.toString())
@@ -66,8 +66,8 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleXmlEntity) {
/** /**
* Reads the bubbles from xml file. * Reads the bubbles from xml file.
*/ */
fun readXml(stream: InputStream): List<BubbleXmlEntity> { fun readXml(stream: InputStream): List<BubbleEntity> {
val bubbles = mutableListOf<BubbleXmlEntity>() val bubbles = mutableListOf<BubbleEntity>()
val parser: XmlPullParser = Xml.newPullParser() val parser: XmlPullParser = Xml.newPullParser()
parser.setInput(stream, StandardCharsets.UTF_8.name()) parser.setInput(stream, StandardCharsets.UTF_8.name())
XmlUtils.beginDocument(parser, TAG_BUBBLES) XmlUtils.beginDocument(parser, TAG_BUBBLES)
@@ -78,9 +78,9 @@ fun readXml(stream: InputStream): List<BubbleXmlEntity> {
return bubbles return bubbles
} }
private fun readXmlEntry(parser: XmlPullParser): BubbleXmlEntity? { private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? {
while (parser.eventType != XmlPullParser.START_TAG) { parser.next() } while (parser.eventType != XmlPullParser.START_TAG) { parser.next() }
return BubbleXmlEntity( return BubbleEntity(
parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null, parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null,
parser.getAttributeWithName(ATTR_PACKAGE) ?: return null, parser.getAttributeWithName(ATTR_PACKAGE) ?: return null,
parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null