Loading packages/SystemUI/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -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", Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +14 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } /** Loading @@ -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); Loading @@ -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 Loading Loading @@ -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; Loading @@ -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() Loading Loading @@ -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) { Loading @@ -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); } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt 0 → 100644 +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) } } } packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -65,6 +66,7 @@ public interface BubbleModule { FeatureFlags featureFlags, DumpManager dumpManager, FloatingContentCoordinator floatingContentCoordinator, BubbleDataRepository bubbleDataRepository, SysUiState sysUiState, INotificationManager notifManager) { return new BubbleController( Loading @@ -84,6 +86,7 @@ public interface BubbleModule { featureFlags, dumpManager, floatingContentCoordinator, bubbleDataRepository, sysUiState, notifManager); } Loading packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt 0 → 100644 +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
packages/SystemUI/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -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", Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +14 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } /** Loading @@ -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); Loading @@ -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 Loading Loading @@ -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; Loading @@ -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() Loading Loading @@ -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) { Loading @@ -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); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt 0 → 100644 +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) } } }
packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -65,6 +66,7 @@ public interface BubbleModule { FeatureFlags featureFlags, DumpManager dumpManager, FloatingContentCoordinator floatingContentCoordinator, BubbleDataRepository bubbleDataRepository, SysUiState sysUiState, INotificationManager notifManager) { return new BubbleController( Loading @@ -84,6 +86,7 @@ public interface BubbleModule { featureFlags, dumpManager, floatingContentCoordinator, bubbleDataRepository, sysUiState, notifManager); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt 0 → 100644 +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"