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

Commit cd3c722e authored by Pinyao Ting's avatar Pinyao Ting Committed by Automerger Merge Worker
Browse files

Merge "Persists bubbles to disk (part 1)" into rvc-dev am: 92703c0e

Change-Id: I6ef9f144e1d6bed2ec0a0442e088058239ee85f9
parents 44a98caa 92703c0e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -129,6 +129,8 @@ android_library {
        "androidx.lifecycle_lifecycle-extensions",
        "androidx.dynamicanimation_dynamicanimation",
        "androidx-constraintlayout_constraintlayout",
        "kotlinx-coroutines-android",
        "kotlinx-coroutines-core",
        "iconloader_base",
        "SystemUI-tags",
        "SystemUI-proto",
+14 −2
Original line number Diff line number Diff line
@@ -153,6 +153,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    private final NotificationGroupManager mNotificationGroupManager;
    private final ShadeController mShadeController;
    private final FloatingContentCoordinator mFloatingContentCoordinator;
    private final BubbleDataRepository mDataRepository;

    private BubbleData mBubbleData;
    private ScrimView mBubbleScrim;
@@ -294,13 +295,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
            FeatureFlags featureFlags,
            DumpManager dumpManager,
            FloatingContentCoordinator floatingContentCoordinator,
            BubbleDataRepository dataRepository,
            SysUiState sysUiState,
            INotificationManager notificationManager) {
        this(context, notificationShadeWindowController, statusBarStateController, shadeController,
                data, null /* synchronizer */, configurationController, interruptionStateProvider,
                zenModeController, notifUserManager, groupManager, entryManager,
                notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState,
                notificationManager);
                notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
                dataRepository, sysUiState, notificationManager);
    }

    /**
@@ -322,6 +324,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
            FeatureFlags featureFlags,
            DumpManager dumpManager,
            FloatingContentCoordinator floatingContentCoordinator,
            BubbleDataRepository dataRepository,
            SysUiState sysUiState,
            INotificationManager notificationManager) {
        dumpManager.registerDumpable(TAG, this);
@@ -331,6 +334,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        mNotifUserManager = notifUserManager;
        mZenModeController = zenModeController;
        mFloatingContentCoordinator = floatingContentCoordinator;
        mDataRepository = dataRepository;
        mINotificationManager = notificationManager;
        mZenModeController.addCallback(new ZenModeController.Callback() {
            @Override
@@ -1018,6 +1022,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
            // Do removals, if any.
            ArrayList<Pair<Bubble, Integer>> removedBubbles =
                    new ArrayList<>(update.removedBubbles);
            ArrayList<Bubble> bubblesToBeRemovedFromRepository = new ArrayList<>();
            for (Pair<Bubble, Integer> removed : removedBubbles) {
                final Bubble bubble = removed.first;
                @DismissReason final int reason = removed.second;
@@ -1027,6 +1032,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                if (reason == DISMISS_USER_CHANGED) {
                    continue;
                }
                if (reason == DISMISS_NOTIF_CANCEL) {
                    bubblesToBeRemovedFromRepository.add(bubble);
                }
                if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
                    if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
                        && (!bubble.showInShade()
@@ -1056,9 +1064,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                    }
                }
            }
            mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);

            if (update.addedBubble != null) {
                mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
                mStackView.addBubble(update.addedBubble);

            }

            if (update.updatedBubble != null) {
@@ -1068,6 +1079,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
            // At this point, the correct bubbles are inflated in the stack.
            // Make sure the order in bubble data is reflected in bubble row.
            if (update.orderChanged) {
                mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
                mStackView.updateBubbleOrder(update.bubbles);
            }

+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.systemui.bubbles

import android.annotation.UserIdInt
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 ioScope = CoroutineScope(Dispatchers.IO)
    private var job: Job? = null

    /**
     * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
     * asynchronously.
     */
    fun addBubble(@UserIdInt userId: Int, bubble: Bubble) {
        volatileRepository.addBubble(
            BubbleXmlEntity(userId, bubble.packageName, bubble.shortcutInfo?.id ?: return))
        persistToDisk()
    }

    /**
     * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
     * asynchronously.
     */
    fun addBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
        volatileRepository.addBubbles(bubbles.mapNotNull {
            val shortcutId = it.shortcutInfo?.id ?: return@mapNotNull null
            BubbleXmlEntity(userId, it.packageName, shortcutId)
        })
        persistToDisk()
    }

    fun removeBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
        volatileRepository.removeBubbles(bubbles.mapNotNull {
            val shortcutId = it.shortcutInfo?.id ?: return@mapNotNull null
            BubbleXmlEntity(userId, it.packageName, shortcutId)
        })
        persistToDisk()
    }

    /**
     * Persists the bubbles to disk. When being called multiple times, it waits for first ongoing
     * write operation to finish then run another write operation exactly once.
     *
     * e.g.
     * Job A started -> blocking I/O
     * Job B started, cancels A, wait for blocking I/O in A finishes
     * Job C started, cancels B, wait for job B to finish
     * Job D started, cancels C, wait for job C to finish
     * Job A completed
     * Job B resumes and reaches yield() and is then cancelled
     * Job C resumes and reaches yield() and is then cancelled
     * Job D resumes and performs another blocking I/O
     */
    private fun persistToDisk() {
        val prev = job
        job = ioScope.launch {
            // if there was an ongoing disk I/O operation, they can be cancelled
            prev?.cancelAndJoin()
            // check for cancellation before disk I/O
            yield()
            // save to disk
            persistentRepository.persistsToDisk(volatileRepository.bubbles)
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.Context;

import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubbleData;
import com.android.systemui.bubbles.BubbleDataRepository;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -65,6 +66,7 @@ public interface BubbleModule {
            FeatureFlags featureFlags,
            DumpManager dumpManager,
            FloatingContentCoordinator floatingContentCoordinator,
            BubbleDataRepository bubbleDataRepository,
            SysUiState sysUiState,
            INotificationManager notifManager) {
        return new BubbleController(
@@ -84,6 +86,7 @@ public interface BubbleModule {
                featureFlags,
                dumpManager,
                floatingContentCoordinator,
                bubbleDataRepository,
                sysUiState,
                notifManager);
    }
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.systemui.bubbles.storage

import android.content.Context
import android.util.AtomicFile
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class BubblePersistentRepository @Inject constructor(
    context: Context
) {

    private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir,
            "overflow_bubbles.xml"), "overflow-bubbles")

    fun persistsToDisk(bubbles: List<BubbleXmlEntity>): Boolean {
        synchronized(bubbleFile) {
            val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) {
                Log.e(TAG, "Failed to save bubble file", e)
                return false
            }
            try {
                writeXml(stream, bubbles)
                bubbleFile.finishWrite(stream)
                return true
            } catch (e: Exception) {
                Log.e(TAG, "Failed to save bubble file, restoring backup", e)
                bubbleFile.failWrite(stream)
            }
        }
        return false
    }
}

private const val TAG = "BubblePersistentRepository"
Loading