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

This commit is contained in:
Pinyao Ting
2020-05-05 09:42:46 +00:00
committed by Android (Google) Code Review
11 changed files with 326 additions and 4 deletions

View File

@@ -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",

View File

@@ -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);
}

View File

@@ -0,0 +1,95 @@
/*
* 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)
}
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,54 @@
/*
* 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"

View File

@@ -0,0 +1,65 @@
/*
* 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 javax.inject.Inject
import javax.inject.Singleton
private const val CAPACITY = 16
/**
* BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory
* manipulation.
*/
@Singleton
class BubbleVolatileRepository @Inject constructor() {
/**
* An ordered set of bubbles based on their natural ordering.
*/
private val entities = mutableSetOf<BubbleXmlEntity>()
/**
* Returns a snapshot of all the bubbles.
*/
val bubbles: List<BubbleXmlEntity>
@Synchronized
get() = entities.toList()
/**
* Add the bubble to memory and perform a de-duplication. In case the bubble already exists,
* the bubble will be moved to the last.
*/
fun addBubble(bubble: BubbleXmlEntity) = addBubbles(listOf(bubble))
/**
* Add the bubbles to memory and perform a de-duplication. In case a bubble already exists,
* it will be moved to the last.
*/
@Synchronized
fun addBubbles(bubbles: List<BubbleXmlEntity>) {
if (bubbles.isEmpty()) return
bubbles.forEach { entities.remove(it) }
if (entities.size + bubbles.size >= CAPACITY) {
entities.drop(entities.size + bubbles.size - CAPACITY)
}
entities.addAll(bubbles.reversed())
}
@Synchronized
fun removeBubbles(bubbles: List<BubbleXmlEntity>) {
bubbles.forEach { entities.remove(it) }
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.annotation.UserIdInt
data class BubbleXmlEntity(
@UserIdInt val userId: Int,
val packageName: String,
val shortcutId: String
)

View File

@@ -0,0 +1,60 @@
/*
* 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 com.android.internal.util.FastXmlSerializer
import org.xmlpull.v1.XmlSerializer
import java.io.IOException
import java.io.OutputStream
import java.nio.charset.StandardCharsets
private const val TAG_BUBBLES = "bs"
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"
/**
* Writes the bubbles in xml format into given output stream.
*/
@Throws(IOException::class)
fun writeXml(stream: OutputStream, bubbles: List<BubbleXmlEntity>) {
val serializer: XmlSerializer = FastXmlSerializer()
serializer.setOutput(stream, StandardCharsets.UTF_8.name())
serializer.startDocument(null, true)
serializer.startTag(null, TAG_BUBBLES)
bubbles.forEach { b -> writeXmlEntry(serializer, b) }
serializer.endTag(null, TAG_BUBBLES)
serializer.endDocument()
}
/**
* Creates a xml entry for given bubble in following format:
* ```
* <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" />
* ```
*/
private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleXmlEntity) {
try {
serializer.startTag(null, TAG_BUBBLE)
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.endTag(null, TAG_BUBBLE)
} catch (e: IOException) {
throw RuntimeException(e)
}
}

View File

@@ -140,6 +140,8 @@ public class BubbleControllerTest extends SysuiTestCase {
private KeyguardBypassController mKeyguardBypassController;
@Mock
private FloatingContentCoordinator mFloatingContentCoordinator;
@Mock
private BubbleDataRepository mDataRepository;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
@@ -275,6 +277,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mFeatureFlagsOldPipeline,
mDumpManager,
mFloatingContentCoordinator,
mDataRepository,
mSysUiState,
mock(INotificationManager.class));
mBubbleController.setExpandListener(mBubbleExpandListener);

View File

@@ -135,6 +135,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
@Mock
private FloatingContentCoordinator mFloatingContentCoordinator;
@Mock
private BubbleDataRepository mDataRepository;
@Mock
private NotificationShadeWindowView mNotificationShadeWindowView;
private SysUiState mSysUiState = new SysUiState();
@@ -250,6 +252,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
mFeatureFlagsNewPipeline,
mDumpManager,
mFloatingContentCoordinator,
mDataRepository,
mSysUiState,
mock(INotificationManager.class));
mBubbleController.addNotifCallback(mNotifCallback);

View File

@@ -55,14 +55,15 @@ public class TestableBubbleController extends BubbleController {
FeatureFlags featureFlags,
DumpManager dumpManager,
FloatingContentCoordinator floatingContentCoordinator,
BubbleDataRepository dataRepository,
SysUiState sysUiState,
INotificationManager notificationManager) {
super(context,
notificationShadeWindowController, statusBarStateController, shadeController,
data, Runnable::run, configurationController, interruptionStateProvider,
zenModeController, lockscreenUserManager, groupManager, entryManager,
notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState,
notificationManager);
notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
dataRepository, sysUiState, notificationManager);
setInflateSynchronously(true);
}
}