Merge "Persists bubbles to disk (part 1)" into rvc-dev
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user