Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +29 −2 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; Loading Loading @@ -147,6 +148,7 @@ public class BubbleController { private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; private final WindowManagerShellWrapper mWindowManagerShellWrapper; private final UserManager mUserManager; private final LauncherApps mLauncherApps; private final IStatusBarService mBarService; private final WindowManager mWindowManager; Loading Loading @@ -231,6 +233,7 @@ public class BubbleController { @Nullable IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, UserManager userManager, LauncherApps launcherApps, TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, Loading @@ -248,8 +251,8 @@ public class BubbleController { BubbleData data = new BubbleData(context, logger, positioner, mainExecutor); return new BubbleController(context, data, synchronizer, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger, taskStackListener, organizer, positioner, displayController, statusBarService, windowManager, windowManagerShellWrapper, userManager, launcherApps, logger, taskStackListener, organizer, positioner, displayController, oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor, taskViewTransitions, syncQueue); } Loading @@ -266,6 +269,7 @@ public class BubbleController { @Nullable IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, UserManager userManager, LauncherApps launcherApps, BubbleLogger bubbleLogger, TaskStackListenerImpl taskStackListener, Loading @@ -287,6 +291,7 @@ public class BubbleController { : statusBarService; mWindowManager = windowManager; mWindowManagerShellWrapper = windowManagerShellWrapper; mUserManager = userManager; mFloatingContentCoordinator = floatingContentCoordinator; mDataRepository = dataRepository; mLogger = bubbleLogger; Loading Loading @@ -447,6 +452,10 @@ public class BubbleController { mOneHandedOptional.ifPresent(this::registerOneHandedState); mDragAndDropController.addListener(this::collapseStack); // Clear out any persisted bubbles on disk that no longer have a valid user. List<UserInfo> users = mUserManager.getAliveUsers(); mDataRepository.sanitizeBubbles(users); } @VisibleForTesting Loading Loading @@ -590,6 +599,17 @@ public class BubbleController { mCurrentProfiles = currentProfiles; } /** Called when a user is removed from the device, including work profiles. */ public void onUserRemoved(int removedUserId) { UserInfo parent = mUserManager.getProfileParent(removedUserId); int parentUserId = parent != null ? parent.getUserHandle().getIdentifier() : -1; mBubbleData.removeBubblesForUser(removedUserId); // Typically calls from BubbleData would remove bubbles from the DataRepository as well, // however, this gets complicated when users are removed (mCurrentUserId won't necessarily // be correct for this) so we update the repo directly. mDataRepository.removeBubblesForUser(removedUserId, parentUserId); } /** Whether this userId belongs to the current user. */ private boolean isCurrentProfile(int userId) { return userId == UserHandle.USER_ALL Loading Loading @@ -1808,6 +1828,13 @@ public class BubbleController { }); } @Override public void onUserRemoved(int removedUserId) { mMainExecutor.execute(() -> { BubbleController.this.onUserRemoved(removedUserId); }); } @Override public void onConfigChanged(Configuration newConfig) { mMainExecutor.execute(() -> { Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +44 −2 Original line number Diff line number Diff line Loading @@ -465,7 +465,7 @@ public class BubbleData { getOverflowBubbles(), invalidBubblesFromPackage, removeBubble); } /** Dismisses all bubbles from the given package. */ /** Removes all bubbles from the given package. */ public void removeBubblesWithPackageName(String packageName, int reason) { final Predicate<Bubble> bubbleMatchesPackage = bubble -> bubble.getPackageName().equals(packageName); Loading @@ -477,6 +477,18 @@ public class BubbleData { performActionOnBubblesMatching(getOverflowBubbles(), bubbleMatchesPackage, removeBubble); } /** Removes all bubbles for the given user. */ public void removeBubblesForUser(int userId) { List<Bubble> removedBubbles = filterAllBubbles(bubble -> userId == bubble.getUser().getIdentifier()); for (Bubble b : removedBubbles) { doRemove(b.getKey(), Bubbles.DISMISS_USER_REMOVED); } if (!removedBubbles.isEmpty()) { dispatchPendingChanges(); } } private void doAdd(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doAdd: " + bubble); Loading Loading @@ -552,7 +564,8 @@ public class BubbleData { || reason == Bubbles.DISMISS_BLOCKED || reason == Bubbles.DISMISS_SHORTCUT_REMOVED || reason == Bubbles.DISMISS_PACKAGE_REMOVED || reason == Bubbles.DISMISS_USER_CHANGED; || reason == Bubbles.DISMISS_USER_CHANGED || reason == Bubbles.DISMISS_USER_REMOVED; int indexToRemove = indexForKey(key); if (indexToRemove == -1) { Loading Loading @@ -1073,6 +1086,35 @@ public class BubbleData { return null; } /** * Returns a list of bubbles that match the provided predicate. This checks all types of * bubbles (i.e. pending, suppressed, active, and overflowed). */ private List<Bubble> filterAllBubbles(Predicate<Bubble> predicate) { ArrayList<Bubble> matchingBubbles = new ArrayList<>(); for (Bubble b : mPendingBubbles.values()) { if (predicate.test(b)) { matchingBubbles.add(b); } } for (Bubble b : mSuppressedBubbles.values()) { if (predicate.test(b)) { matchingBubbles.add(b); } } for (Bubble b : mBubbles) { if (predicate.test(b)) { matchingBubbles.add(b); } } for (Bubble b : mOverflowBubbles) { if (predicate.test(b)) { matchingBubbles.add(b); } } return matchingBubbles; } @VisibleForTesting(visibility = PRIVATE) void setTimeSource(TimeSource timeSource) { mTimeSource = timeSource; Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +17 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER import android.content.pm.UserInfo import android.os.UserHandle import android.util.Log import com.android.wm.shell.bubbles.storage.BubbleEntity Loading Loading @@ -73,6 +74,22 @@ internal class BubbleDataRepository( if (entities.isNotEmpty()) persistToDisk() } /** * Removes all the bubbles associated with the provided user from memory. Then persists the * snapshot to disk asynchronously. */ fun removeBubblesForUser(@UserIdInt userId: Int, @UserIdInt parentId: Int) { if (volatileRepository.removeBubblesForUser(userId, parentId)) persistToDisk() } /** * Remove any bubbles that don't have a user id from the provided list of users. */ fun sanitizeBubbles(users: List<UserInfo>) { val userIds = users.map { u -> u.id } if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk() } private fun transform(bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> BubbleEntity( Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +9 −1 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ public interface Bubbles { DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE, DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT, DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED, DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK}) DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED}) @Target({FIELD, LOCAL_VARIABLE, PARAMETER}) @interface DismissReason {} Loading @@ -76,6 +76,7 @@ public interface Bubbles { int DISMISS_PACKAGE_REMOVED = 13; int DISMISS_NO_BUBBLE_UP = 14; int DISMISS_RELOAD_FROM_DISK = 15; int DISMISS_USER_REMOVED = 16; /** * @return {@code true} if there is a bubble associated with the provided key and if its Loading Loading @@ -242,6 +243,13 @@ public interface Bubbles { */ void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles); /** * Called when a user is removed. * * @param removedUserId the id of the removed user. */ void onUserRemoved(int removedUserId); /** * Called when config changed. * Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt +55 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.wm.shell.bubbles.storage import android.annotation.UserIdInt import android.content.pm.LauncherApps import android.os.UserHandle import android.util.SparseArray Loading Loading @@ -95,10 +96,63 @@ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { } @Synchronized fun removeBubbles(userId: Int, bubbles: List<BubbleEntity>) = fun removeBubbles(@UserIdInt userId: Int, bubbles: List<BubbleEntity>) = uncache(bubbles.filter { b: BubbleEntity -> getEntities(userId).removeIf { e: BubbleEntity -> b.key == e.key } }) /** * Removes all the bubbles associated with the provided userId. * @return whether bubbles were removed or not. */ @Synchronized fun removeBubblesForUser(@UserIdInt userId: Int, @UserIdInt parentUserId: Int): Boolean { if (parentUserId != -1) { return removeBubblesForUserWithParent(userId, parentUserId) } else { val entities = entitiesByUser.get(userId) entitiesByUser.remove(userId) return entities != null } } /** * Removes all the bubbles associated with the provided userId when that userId is part of * a profile (e.g. managed account). * * @return whether bubbles were removed or not. */ @Synchronized private fun removeBubblesForUserWithParent( @UserIdInt userId: Int, @UserIdInt parentUserId: Int ): Boolean { return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> b.userId == userId } } /** * Goes through all the persisted bubbles and removes them if the user is not in the active * list of users. * * @return whether the list of bubbles changed or not (i.e. was a removal made). */ @Synchronized fun sanitizeBubbles(activeUsers: List<Int>): Boolean { for (i in 0 until entitiesByUser.size()) { // First check if the user is a parent / top-level user val parentUserId = entitiesByUser.keyAt(i) if (!activeUsers.contains(parentUserId)) { return removeBubblesForUser(parentUserId, -1) } else { // Then check if each of the bubbles in the top-level user, still has a valid user // as it could belong to a profile and have a different id from the parent. return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> !activeUsers.contains(b.userId) } } } return false } private fun cache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +29 −2 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; Loading Loading @@ -147,6 +148,7 @@ public class BubbleController { private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; private final WindowManagerShellWrapper mWindowManagerShellWrapper; private final UserManager mUserManager; private final LauncherApps mLauncherApps; private final IStatusBarService mBarService; private final WindowManager mWindowManager; Loading Loading @@ -231,6 +233,7 @@ public class BubbleController { @Nullable IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, UserManager userManager, LauncherApps launcherApps, TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, Loading @@ -248,8 +251,8 @@ public class BubbleController { BubbleData data = new BubbleData(context, logger, positioner, mainExecutor); return new BubbleController(context, data, synchronizer, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger, taskStackListener, organizer, positioner, displayController, statusBarService, windowManager, windowManagerShellWrapper, userManager, launcherApps, logger, taskStackListener, organizer, positioner, displayController, oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor, taskViewTransitions, syncQueue); } Loading @@ -266,6 +269,7 @@ public class BubbleController { @Nullable IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, UserManager userManager, LauncherApps launcherApps, BubbleLogger bubbleLogger, TaskStackListenerImpl taskStackListener, Loading @@ -287,6 +291,7 @@ public class BubbleController { : statusBarService; mWindowManager = windowManager; mWindowManagerShellWrapper = windowManagerShellWrapper; mUserManager = userManager; mFloatingContentCoordinator = floatingContentCoordinator; mDataRepository = dataRepository; mLogger = bubbleLogger; Loading Loading @@ -447,6 +452,10 @@ public class BubbleController { mOneHandedOptional.ifPresent(this::registerOneHandedState); mDragAndDropController.addListener(this::collapseStack); // Clear out any persisted bubbles on disk that no longer have a valid user. List<UserInfo> users = mUserManager.getAliveUsers(); mDataRepository.sanitizeBubbles(users); } @VisibleForTesting Loading Loading @@ -590,6 +599,17 @@ public class BubbleController { mCurrentProfiles = currentProfiles; } /** Called when a user is removed from the device, including work profiles. */ public void onUserRemoved(int removedUserId) { UserInfo parent = mUserManager.getProfileParent(removedUserId); int parentUserId = parent != null ? parent.getUserHandle().getIdentifier() : -1; mBubbleData.removeBubblesForUser(removedUserId); // Typically calls from BubbleData would remove bubbles from the DataRepository as well, // however, this gets complicated when users are removed (mCurrentUserId won't necessarily // be correct for this) so we update the repo directly. mDataRepository.removeBubblesForUser(removedUserId, parentUserId); } /** Whether this userId belongs to the current user. */ private boolean isCurrentProfile(int userId) { return userId == UserHandle.USER_ALL Loading Loading @@ -1808,6 +1828,13 @@ public class BubbleController { }); } @Override public void onUserRemoved(int removedUserId) { mMainExecutor.execute(() -> { BubbleController.this.onUserRemoved(removedUserId); }); } @Override public void onConfigChanged(Configuration newConfig) { mMainExecutor.execute(() -> { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +44 −2 Original line number Diff line number Diff line Loading @@ -465,7 +465,7 @@ public class BubbleData { getOverflowBubbles(), invalidBubblesFromPackage, removeBubble); } /** Dismisses all bubbles from the given package. */ /** Removes all bubbles from the given package. */ public void removeBubblesWithPackageName(String packageName, int reason) { final Predicate<Bubble> bubbleMatchesPackage = bubble -> bubble.getPackageName().equals(packageName); Loading @@ -477,6 +477,18 @@ public class BubbleData { performActionOnBubblesMatching(getOverflowBubbles(), bubbleMatchesPackage, removeBubble); } /** Removes all bubbles for the given user. */ public void removeBubblesForUser(int userId) { List<Bubble> removedBubbles = filterAllBubbles(bubble -> userId == bubble.getUser().getIdentifier()); for (Bubble b : removedBubbles) { doRemove(b.getKey(), Bubbles.DISMISS_USER_REMOVED); } if (!removedBubbles.isEmpty()) { dispatchPendingChanges(); } } private void doAdd(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doAdd: " + bubble); Loading Loading @@ -552,7 +564,8 @@ public class BubbleData { || reason == Bubbles.DISMISS_BLOCKED || reason == Bubbles.DISMISS_SHORTCUT_REMOVED || reason == Bubbles.DISMISS_PACKAGE_REMOVED || reason == Bubbles.DISMISS_USER_CHANGED; || reason == Bubbles.DISMISS_USER_CHANGED || reason == Bubbles.DISMISS_USER_REMOVED; int indexToRemove = indexForKey(key); if (indexToRemove == -1) { Loading Loading @@ -1073,6 +1086,35 @@ public class BubbleData { return null; } /** * Returns a list of bubbles that match the provided predicate. This checks all types of * bubbles (i.e. pending, suppressed, active, and overflowed). */ private List<Bubble> filterAllBubbles(Predicate<Bubble> predicate) { ArrayList<Bubble> matchingBubbles = new ArrayList<>(); for (Bubble b : mPendingBubbles.values()) { if (predicate.test(b)) { matchingBubbles.add(b); } } for (Bubble b : mSuppressedBubbles.values()) { if (predicate.test(b)) { matchingBubbles.add(b); } } for (Bubble b : mBubbles) { if (predicate.test(b)) { matchingBubbles.add(b); } } for (Bubble b : mOverflowBubbles) { if (predicate.test(b)) { matchingBubbles.add(b); } } return matchingBubbles; } @VisibleForTesting(visibility = PRIVATE) void setTimeSource(TimeSource timeSource) { mTimeSource = timeSource; Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +17 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER import android.content.pm.UserInfo import android.os.UserHandle import android.util.Log import com.android.wm.shell.bubbles.storage.BubbleEntity Loading Loading @@ -73,6 +74,22 @@ internal class BubbleDataRepository( if (entities.isNotEmpty()) persistToDisk() } /** * Removes all the bubbles associated with the provided user from memory. Then persists the * snapshot to disk asynchronously. */ fun removeBubblesForUser(@UserIdInt userId: Int, @UserIdInt parentId: Int) { if (volatileRepository.removeBubblesForUser(userId, parentId)) persistToDisk() } /** * Remove any bubbles that don't have a user id from the provided list of users. */ fun sanitizeBubbles(users: List<UserInfo>) { val userIds = users.map { u -> u.id } if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk() } private fun transform(bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> BubbleEntity( Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +9 −1 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ public interface Bubbles { DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE, DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT, DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED, DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK}) DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED}) @Target({FIELD, LOCAL_VARIABLE, PARAMETER}) @interface DismissReason {} Loading @@ -76,6 +76,7 @@ public interface Bubbles { int DISMISS_PACKAGE_REMOVED = 13; int DISMISS_NO_BUBBLE_UP = 14; int DISMISS_RELOAD_FROM_DISK = 15; int DISMISS_USER_REMOVED = 16; /** * @return {@code true} if there is a bubble associated with the provided key and if its Loading Loading @@ -242,6 +243,13 @@ public interface Bubbles { */ void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles); /** * Called when a user is removed. * * @param removedUserId the id of the removed user. */ void onUserRemoved(int removedUserId); /** * Called when config changed. * Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt +55 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.wm.shell.bubbles.storage import android.annotation.UserIdInt import android.content.pm.LauncherApps import android.os.UserHandle import android.util.SparseArray Loading Loading @@ -95,10 +96,63 @@ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { } @Synchronized fun removeBubbles(userId: Int, bubbles: List<BubbleEntity>) = fun removeBubbles(@UserIdInt userId: Int, bubbles: List<BubbleEntity>) = uncache(bubbles.filter { b: BubbleEntity -> getEntities(userId).removeIf { e: BubbleEntity -> b.key == e.key } }) /** * Removes all the bubbles associated with the provided userId. * @return whether bubbles were removed or not. */ @Synchronized fun removeBubblesForUser(@UserIdInt userId: Int, @UserIdInt parentUserId: Int): Boolean { if (parentUserId != -1) { return removeBubblesForUserWithParent(userId, parentUserId) } else { val entities = entitiesByUser.get(userId) entitiesByUser.remove(userId) return entities != null } } /** * Removes all the bubbles associated with the provided userId when that userId is part of * a profile (e.g. managed account). * * @return whether bubbles were removed or not. */ @Synchronized private fun removeBubblesForUserWithParent( @UserIdInt userId: Int, @UserIdInt parentUserId: Int ): Boolean { return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> b.userId == userId } } /** * Goes through all the persisted bubbles and removes them if the user is not in the active * list of users. * * @return whether the list of bubbles changed or not (i.e. was a removal made). */ @Synchronized fun sanitizeBubbles(activeUsers: List<Int>): Boolean { for (i in 0 until entitiesByUser.size()) { // First check if the user is a parent / top-level user val parentUserId = entitiesByUser.keyAt(i) if (!activeUsers.contains(parentUserId)) { return removeBubblesForUser(parentUserId, -1) } else { // Then check if each of the bubbles in the top-level user, still has a valid user // as it could belong to a profile and have a different id from the parent. return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> !activeUsers.contains(b.userId) } } } return false } private fun cache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, Loading