Merge "Persists bubbles to disk (part 3)" into rvc-dev am: 8e65e07631
Change-Id: I97a6606d5d7afe0ef428b613d9533300275f2be5
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user