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

Commit 6a8fab05 authored by Pinyao Ting's avatar Pinyao Ting
Browse files

Prevent duplicates from persisted bubbles.

Bug: 157070577
Test: add some bubbles, reboot, verify it doesn't generate duplicates
Change-Id: I95898fa6a1c54e2e57fde5990ba6cfefd1a5c2f0
parent 3c930613
Loading
Loading
Loading
Loading
+5 −26
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */
package com.android.systemui.bubbles;


import static android.app.Notification.FLAG_BUBBLE;
import static android.os.AsyncTask.Status.FINISHED;
import static android.view.Display.INVALID_DISPLAY;
@@ -105,35 +104,15 @@ class Bubble implements BubbleViewProvider {
    private Path mDotPath;
    private int mFlags;

    /**
     * Generate a unique identifier for this bubble based on given {@link NotificationEntry}. If
     * {@link ShortcutInfo} was found in the notification entry, the identifier would be generated
     * from {@link ShortcutInfo} instead. See also {@link #key(ShortcutInfo)}.
     */
    @NonNull
    public static String key(@NonNull final NotificationEntry entry) {
        final ShortcutInfo shortcutInfo = entry.getRanking().getShortcutInfo();
        if (shortcutInfo != null) return key(shortcutInfo);
        return entry.getKey();
    }

    /**
     * Generate a unique identifier for this bubble based on given {@link ShortcutInfo}.
     * See also {@link #key(NotificationEntry)}.
     */
    @NonNull
    public static String key(@NonNull final ShortcutInfo shortcutInfo) {
        return shortcutInfo.getUserId() + "|" + shortcutInfo.getPackage() + "|"
                + shortcutInfo.getId();
    }

    /**
     * Create a bubble with limited information based on given {@link ShortcutInfo}.
     * Note: Currently this is only being used when the bubble is persisted to disk.
     */
    Bubble(ShortcutInfo shortcutInfo) {
    Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo) {
        Objects.requireNonNull(key);
        Objects.requireNonNull(shortcutInfo);
        mShortcutInfo = shortcutInfo;
        mKey = key(shortcutInfo);
        mKey = key;
        mFlags = 0;
    }

@@ -142,7 +121,7 @@ class Bubble implements BubbleViewProvider {
    Bubble(NotificationEntry e,
            BubbleController.NotificationSuppressionChangedListener listener) {
        mEntry = e;
        mKey = key(e);
        mKey = e.getKey();
        mLastUpdated = e.getSbn().getPostTime();
        mSuppressionListener = listener;
        mFlags = e.getSbn().getNotification().flags;
+2 −2
Original line number Diff line number Diff line
@@ -606,8 +606,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi

            mStackView.setUnbubbleConversationCallback(notificationEntry ->
                    onUserChangedBubble(notificationEntry, false /* shouldBubble */));
            // Lazy load overflow bubbles from disk
            loadOverflowBubblesFromDisk();
        }

        addToWindowManagerMaybe();
@@ -1112,6 +1110,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi

        @Override
        public void applyUpdate(BubbleData.Update update) {
            // Lazy load overflow bubbles from disk
            loadOverflowBubblesFromDisk();
            // Update bubbles in overflow.
            if (mOverflowCallback != null) {
                mOverflowCallback.run();
+14 −10
Original line number Diff line number Diff line
@@ -77,7 +77,7 @@ internal class BubbleDataRepository @Inject constructor(
            var shortcutId = b.shortcutInfo?.id
            if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId
            if (shortcutId == null) return@mapNotNull null
            BubbleEntity(userId, b.packageName, shortcutId)
            BubbleEntity(userId, b.packageName, shortcutId, b.key)
        }
    }

@@ -133,17 +133,17 @@ internal class BubbleDataRepository @Inject constructor(
        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.
         * from the userId/packageName pair to a list of associated ShortcutInfo.
         * e.g.
         * {
         *     BubbleEntity(0, "com.example.messenger", "id-0") ->
         *     ShortcutKey(0, "com.example.messenger") -> [
         *         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=0, pkg="com.example.messenger", id="id-2")
         *     ]
         *     ShortcutKey(10, "com.example.chat") -> [
         *         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 ->
@@ -151,11 +151,15 @@ internal class BubbleDataRepository @Inject constructor(
                    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()
                    ?: emptyList()
        }.groupBy { ShortcutKey(it.userId, it.`package`) }
        // 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) } }
        val bubbles = entities.mapNotNull { entity ->
            shortcutMap[ShortcutKey(entity.userId, entity.packageName)]
                    ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
                    ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo) }
        }
        uiScope.launch { cb(bubbles) }
    }

+2 −1
Original line number Diff line number Diff line
@@ -20,5 +20,6 @@ import android.annotation.UserIdInt
data class BubbleEntity(
    @UserIdInt val userId: Int,
    val packageName: String,
    val shortcutId: String
    val shortcutId: String,
    val key: String
)
+5 −2
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ private const val TAG_BUBBLE = "bb"
private const val ATTR_USER_ID = "uid"
private const val ATTR_PACKAGE = "pkg"
private const val ATTR_SHORTCUT_ID = "sid"
private const val ATTR_KEY = "key"

/**
 * Writes the bubbles in xml format into given output stream.
@@ -48,7 +49,7 @@ fun writeXml(stream: OutputStream, bubbles: List<BubbleEntity>) {
/**
 * Creates a xml entry for given bubble in following format:
 * ```
 * <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" />
 * <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" key="my-key" />
 * ```
 */
private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
@@ -57,6 +58,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
        serializer.attribute(null, ATTR_USER_ID, bubble.userId.toString())
        serializer.attribute(null, ATTR_PACKAGE, bubble.packageName)
        serializer.attribute(null, ATTR_SHORTCUT_ID, bubble.shortcutId)
        serializer.attribute(null, ATTR_KEY, bubble.key)
        serializer.endTag(null, TAG_BUBBLE)
    } catch (e: IOException) {
        throw RuntimeException(e)
@@ -83,7 +85,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? {
    return BubbleEntity(
            parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null,
            parser.getAttributeWithName(ATTR_PACKAGE) ?: return null,
            parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null
            parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null,
            parser.getAttributeWithName(ATTR_KEY) ?: return null
    )
}

Loading