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();
}
// 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. */
@VisibleForTesting(visibility = PRIVATE)
Bubble(NotificationEntry e,

View File

@@ -15,24 +15,32 @@
*/
package com.android.systemui.bubbles
import android.annotation.SuppressLint
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 com.android.systemui.bubbles.storage.BubbleEntity
import com.android.systemui.bubbles.storage.BubblePersistentRepository
import com.android.systemui.bubbles.storage.BubbleVolatileRepository
import com.android.systemui.bubbles.storage.BubbleXmlEntity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class BubbleDataRepository @Inject constructor(
private val volatileRepository: BubbleVolatileRepository,
private val persistentRepository: BubblePersistentRepository
private val persistentRepository: BubblePersistentRepository,
private val launcherApps: LauncherApps
) {
private val ioScope = CoroutineScope(Dispatchers.IO)
@@ -64,10 +72,10 @@ internal class BubbleDataRepository @Inject constructor(
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 ->
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.
*/
// TODO: call this method from BubbleController and update UI
@SuppressLint("WrongConstant")
fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch {
val bubbleXmlEntities = persistentRepository.readFromDisk()
volatileRepository.addBubbles(bubbleXmlEntities)
uiScope.launch {
// TODO: transform bubbleXmlEntities into bubbles
// cb(bubbles)
}
/**
* Load BubbleEntity from disk.
* e.g.
* [
* 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 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
data class BubbleXmlEntity(
data class BubbleEntity(
@UserIdInt val userId: Int,
val packageName: String,
val shortcutId: String

View File

@@ -32,7 +32,7 @@ class BubblePersistentRepository @Inject constructor(
private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir,
"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")
synchronized(bubbleFile) {
val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) {
@@ -52,7 +52,7 @@ class BubblePersistentRepository @Inject constructor(
return false
}
fun readFromDisk(): List<BubbleXmlEntity> {
fun readFromDisk(): List<BubbleEntity> {
synchronized(bubbleFile) {
try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) {
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.
*/
private val entities = mutableSetOf<BubbleXmlEntity>()
private val entities = mutableSetOf<BubbleEntity>()
/**
* Returns a snapshot of all the bubbles.
*/
val bubbles: List<BubbleXmlEntity>
val bubbles: List<BubbleEntity>
@Synchronized
get() = entities.toList()
@@ -43,7 +43,7 @@ class BubbleVolatileRepository @Inject constructor() {
* it will be moved to the last.
*/
@Synchronized
fun addBubbles(bubbles: List<BubbleXmlEntity>) {
fun addBubbles(bubbles: List<BubbleEntity>) {
if (bubbles.isEmpty()) return
bubbles.forEach { entities.remove(it) }
if (entities.size + bubbles.size >= CAPACITY) {
@@ -53,7 +53,7 @@ class BubbleVolatileRepository @Inject constructor() {
}
@Synchronized
fun removeBubbles(bubbles: List<BubbleXmlEntity>) {
fun removeBubbles(bubbles: List<BubbleEntity>) {
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.
*/
@Throws(IOException::class)
fun writeXml(stream: OutputStream, bubbles: List<BubbleXmlEntity>) {
fun writeXml(stream: OutputStream, bubbles: List<BubbleEntity>) {
val serializer: XmlSerializer = FastXmlSerializer()
serializer.setOutput(stream, StandardCharsets.UTF_8.name())
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" />
* ```
*/
private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleXmlEntity) {
private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
try {
serializer.startTag(null, TAG_BUBBLE)
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.
*/
fun readXml(stream: InputStream): List<BubbleXmlEntity> {
val bubbles = mutableListOf<BubbleXmlEntity>()
fun readXml(stream: InputStream): List<BubbleEntity> {
val bubbles = mutableListOf<BubbleEntity>()
val parser: XmlPullParser = Xml.newPullParser()
parser.setInput(stream, StandardCharsets.UTF_8.name())
XmlUtils.beginDocument(parser, TAG_BUBBLES)
@@ -78,9 +78,9 @@ fun readXml(stream: InputStream): List<BubbleXmlEntity> {
return bubbles
}
private fun readXmlEntry(parser: XmlPullParser): BubbleXmlEntity? {
private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? {
while (parser.eventType != XmlPullParser.START_TAG) { parser.next() }
return BubbleXmlEntity(
return BubbleEntity(
parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null,
parser.getAttributeWithName(ATTR_PACKAGE) ?: return null,
parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null