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

Commit 92703c0e authored by Pinyao Ting's avatar Pinyao Ting Committed by Android (Google) Code Review
Browse files

Merge "Persists bubbles to disk (part 1)" into rvc-dev

parents 8011ca61 ee191b1c
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