Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d4c1ba91 authored by Pinyao Ting's avatar Pinyao Ting
Browse files

Persists bubbles to disk (part 3)

Handle ETL of bubbles from xml file and retrieve corresponding ShortcutInfo.

Bug: 149713060
Test: call loadBubbles() and verify it fetches the shortcuts
Change-Id: I3d49f494b402ba6eacb4825c0bd3c4b377428a2a
parent a60a5d7a
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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,
+63 −10
Original line number Diff line number Diff line
@@ -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
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -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
+2 −2
Original line number Diff line number Diff line
@@ -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)
+4 −4
Original line number Diff line number Diff line
@@ -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) }
    }
}
Loading