When a user is removed make sure we remove their persisted bubbles
- Update the NotificationLockscreenUserManager code to listen for account removals and notify the new listener method onUserRemoved - Use this to trigger removals in the bubble data repository / UI Some users may have a parent user (e.g. work profile can belong to a parent user). We store persisted bubbles under the parent user and have a separate field for the specific bubble user. To support this, I pass the parent of the removed user. Test: atest BubbleVolatileRepositoryTest BubbleDataTest Bug: 218525763 Change-Id: Ia147cbac15ebce70bbbcc52926089f33901a99dc Merged-In: Ia147cbac15ebce70bbbcc52926089f33901a99dc
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -231,6 +233,7 @@ public class BubbleController {
|
||||
@Nullable IStatusBarService statusBarService,
|
||||
WindowManager windowManager,
|
||||
WindowManagerShellWrapper windowManagerShellWrapper,
|
||||
UserManager userManager,
|
||||
LauncherApps launcherApps,
|
||||
TaskStackListenerImpl taskStackListener,
|
||||
UiEventLogger uiEventLogger,
|
||||
@@ -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);
|
||||
}
|
||||
@@ -266,6 +269,7 @@ public class BubbleController {
|
||||
@Nullable IStatusBarService statusBarService,
|
||||
WindowManager windowManager,
|
||||
WindowManagerShellWrapper windowManagerShellWrapper,
|
||||
UserManager userManager,
|
||||
LauncherApps launcherApps,
|
||||
BubbleLogger bubbleLogger,
|
||||
TaskStackListenerImpl taskStackListener,
|
||||
@@ -287,6 +291,7 @@ public class BubbleController {
|
||||
: statusBarService;
|
||||
mWindowManager = windowManager;
|
||||
mWindowManagerShellWrapper = windowManagerShellWrapper;
|
||||
mUserManager = userManager;
|
||||
mFloatingContentCoordinator = floatingContentCoordinator;
|
||||
mDataRepository = dataRepository;
|
||||
mLogger = bubbleLogger;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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(() -> {
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
@@ -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 },
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.animation.AnimationHandler;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.os.Handler;
|
||||
import android.os.UserManager;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
@@ -106,6 +107,7 @@ public class WMShellModule {
|
||||
IStatusBarService statusBarService,
|
||||
WindowManager windowManager,
|
||||
WindowManagerShellWrapper windowManagerShellWrapper,
|
||||
UserManager userManager,
|
||||
LauncherApps launcherApps,
|
||||
TaskStackListenerImpl taskStackListener,
|
||||
UiEventLogger uiEventLogger,
|
||||
@@ -120,7 +122,7 @@ public class WMShellModule {
|
||||
SyncTransactionQueue syncQueue) {
|
||||
return BubbleController.create(context, null /* synchronizer */,
|
||||
floatingContentCoordinator, statusBarService, windowManager,
|
||||
windowManagerShellWrapper, launcherApps, taskStackListener,
|
||||
windowManagerShellWrapper, userManager, launcherApps, taskStackListener,
|
||||
uiEventLogger, organizer, displayController, oneHandedOptional,
|
||||
dragAndDropController, mainExecutor, mainHandler, bgExecutor,
|
||||
taskViewTransitions, syncQueue);
|
||||
|
||||
@@ -129,8 +129,8 @@ public class BubbleDataTest extends ShellTestCase {
|
||||
mEntryA3 = createBubbleEntry(1, "a3", "package.a", null);
|
||||
mEntryB1 = createBubbleEntry(1, "b1", "package.b", null);
|
||||
mEntryB2 = createBubbleEntry(1, "b2", "package.b", null);
|
||||
mEntryB3 = createBubbleEntry(1, "b3", "package.b", null);
|
||||
mEntryC1 = createBubbleEntry(1, "c1", "package.c", null);
|
||||
mEntryB3 = createBubbleEntry(11, "b3", "package.b", null);
|
||||
mEntryC1 = createBubbleEntry(11, "c1", "package.c", null);
|
||||
|
||||
NotificationListenerService.Ranking ranking =
|
||||
mock(NotificationListenerService.Ranking.class);
|
||||
@@ -1058,6 +1058,37 @@ public class BubbleDataTest extends ShellTestCase {
|
||||
assertBubbleListContains(mBubbleA2, mBubbleA1, mBubbleLocusId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_removeBubblesForUser() {
|
||||
// A is user 1
|
||||
sendUpdatedEntryAtTime(mEntryA1, 2000);
|
||||
sendUpdatedEntryAtTime(mEntryA2, 3000);
|
||||
// B & C belong to user 11
|
||||
sendUpdatedEntryAtTime(mEntryB3, 4000);
|
||||
sendUpdatedEntryAtTime(mEntryC1, 5000);
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
|
||||
assertBubbleListContains(mBubbleC1, mBubbleB3, mBubbleA2);
|
||||
|
||||
// Remove all the A bubbles
|
||||
mBubbleData.removeBubblesForUser(1);
|
||||
verifyUpdateReceived();
|
||||
|
||||
// Verify the update has the removals.
|
||||
BubbleData.Update update = mUpdateCaptor.getValue();
|
||||
assertThat(update.removedBubbles.get(0)).isEqualTo(
|
||||
Pair.create(mBubbleA2, Bubbles.DISMISS_USER_REMOVED));
|
||||
assertThat(update.removedBubbles.get(1)).isEqualTo(
|
||||
Pair.create(mBubbleA1, Bubbles.DISMISS_USER_REMOVED));
|
||||
|
||||
// Verify no A bubbles in active or overflow.
|
||||
assertBubbleListContains(mBubbleC1, mBubbleB3);
|
||||
assertOverflowChangedTo(ImmutableList.of());
|
||||
}
|
||||
|
||||
private void verifyUpdateReceived() {
|
||||
verify(mListener).applyUpdate(mUpdateCaptor.capture());
|
||||
reset(mListener);
|
||||
|
||||
@@ -23,14 +23,19 @@ import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import org.junit.Test
|
||||
import com.android.wm.shell.ShellTestCase
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -41,17 +46,17 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
|
||||
private val user11 = UserHandle.of(11)
|
||||
|
||||
// user, package, shortcut, notification key, height, res-height, title, taskId, locusId
|
||||
private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1",
|
||||
"0key-1", 120, 0, null, 1, null)
|
||||
private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob",
|
||||
"10key-2", 0, 16537428, "title", 2, null)
|
||||
private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2",
|
||||
"0key-3", 120, 0, null, INVALID_TASK_ID, null)
|
||||
private val bubble1 = BubbleEntity(user0.identifier,
|
||||
"com.example.messenger", "shortcut-1", "0key-1", 120, 0, null, 1, null)
|
||||
private val bubble2 = BubbleEntity(user10_managed.identifier,
|
||||
"com.example.chat", "alice and bob", "10key-2", 0, 16537428, "title", 2, null)
|
||||
private val bubble3 = BubbleEntity(user0.identifier,
|
||||
"com.example.messenger", "shortcut-2", "0key-3", 120, 0, null, INVALID_TASK_ID, null)
|
||||
|
||||
private val bubble11 = BubbleEntity(11, "com.example.messenger",
|
||||
"shortcut-1", "01key-1", 120, 0, null, 3)
|
||||
private val bubble12 = BubbleEntity(11, "com.example.chat", "alice and bob",
|
||||
"11key-2", 0, 16537428, "title", INVALID_TASK_ID)
|
||||
private val bubble11 = BubbleEntity(user11.identifier,
|
||||
"com.example.messenger", "shortcut-1", "01key-1", 120, 0, null, 3)
|
||||
private val bubble12 = BubbleEntity(user11.identifier,
|
||||
"com.example.chat", "alice and bob", "11key-2", 0, 16537428, "title", INVALID_TASK_ID)
|
||||
|
||||
private val user0bubbles = listOf(bubble1, bubble2, bubble3)
|
||||
private val user11bubbles = listOf(bubble11, bubble12)
|
||||
@@ -151,6 +156,119 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
|
||||
repository.addBubbles(user0.identifier, listOf(bubbleModified))
|
||||
assertEquals(bubbleModified, repository.getEntities(user0.identifier).get(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveBubblesForUser() {
|
||||
repository.addBubbles(user0.identifier, user0bubbles)
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble2, bubble3))
|
||||
|
||||
val ret = repository.removeBubblesForUser(user0.identifier, -1)
|
||||
assertThat(ret).isTrue() // bubbles were removed
|
||||
|
||||
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
|
||||
verify(launcherApps, never()).uncacheShortcuts(anyString(),
|
||||
any(),
|
||||
any(UserHandle::class.java), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveBubblesForUser_parentUserRemoved() {
|
||||
repository.addBubbles(user0.identifier, user0bubbles)
|
||||
// bubble2 is the work profile bubble
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble2, bubble3))
|
||||
|
||||
val ret = repository.removeBubblesForUser(user10_managed.identifier, user0.identifier)
|
||||
assertThat(ret).isTrue() // bubbles were removed
|
||||
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble3))
|
||||
verify(launcherApps, never()).uncacheShortcuts(anyString(),
|
||||
any(),
|
||||
any(UserHandle::class.java), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveBubblesForUser_withoutBubbles() {
|
||||
repository.addBubbles(user0.identifier, user0bubbles)
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble2, bubble3))
|
||||
|
||||
val ret = repository.removeBubblesForUser(user11.identifier, -1)
|
||||
assertThat(ret).isFalse() // bubbles were NOT removed
|
||||
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble2, bubble3))
|
||||
verify(launcherApps, never()).uncacheShortcuts(anyString(),
|
||||
any(),
|
||||
any(UserHandle::class.java), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSanitizeBubbles_noChanges() {
|
||||
repository.addBubbles(user0.identifier, user0bubbles)
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble2, bubble3))
|
||||
repository.addBubbles(user11.identifier, user11bubbles)
|
||||
assertThat(repository.getEntities(user11.identifier).toList())
|
||||
.isEqualTo(listOf(bubble11, bubble12))
|
||||
|
||||
val ret = repository.sanitizeBubbles(listOf(user0.identifier,
|
||||
user10_managed.identifier,
|
||||
user11.identifier))
|
||||
assertThat(ret).isFalse() // bubbles were NOT removed
|
||||
|
||||
verify(launcherApps, never()).uncacheShortcuts(anyString(),
|
||||
any(),
|
||||
any(UserHandle::class.java), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSanitizeBubbles_userRemoved() {
|
||||
repository.addBubbles(user0.identifier, user0bubbles)
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble2, bubble3))
|
||||
repository.addBubbles(user11.identifier, user11bubbles)
|
||||
assertThat(repository.getEntities(user11.identifier).toList())
|
||||
.isEqualTo(listOf(bubble11, bubble12))
|
||||
|
||||
val ret = repository.sanitizeBubbles(listOf(user11.identifier))
|
||||
assertThat(ret).isTrue() // bubbles were removed
|
||||
|
||||
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
|
||||
verify(launcherApps, never()).uncacheShortcuts(anyString(),
|
||||
any(),
|
||||
any(UserHandle::class.java), anyInt())
|
||||
|
||||
// User 11 bubbles should still be here
|
||||
assertThat(repository.getEntities(user11.identifier).toList())
|
||||
.isEqualTo(listOf(bubble11, bubble12))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSanitizeBubbles_userParentRemoved() {
|
||||
repository.addBubbles(user0.identifier, user0bubbles)
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble2, bubble3))
|
||||
|
||||
repository.addBubbles(user11.identifier, user11bubbles)
|
||||
assertThat(repository.getEntities(user11.identifier).toList())
|
||||
.isEqualTo(listOf(bubble11, bubble12))
|
||||
|
||||
val ret = repository.sanitizeBubbles(listOf(user0.identifier, user11.identifier))
|
||||
assertThat(ret).isTrue() // bubbles were removed
|
||||
// bubble2 is the work profile bubble and should be removed
|
||||
assertThat(repository.getEntities(user0.identifier).toList())
|
||||
.isEqualTo(listOf(bubble1, bubble3))
|
||||
verify(launcherApps, never()).uncacheShortcuts(anyString(),
|
||||
any(),
|
||||
any(UserHandle::class.java), anyInt())
|
||||
|
||||
// User 11 bubbles should still be here
|
||||
assertThat(repository.getEntities(user11.identifier).toList())
|
||||
.isEqualTo(listOf(bubble11, bubble12))
|
||||
}
|
||||
}
|
||||
|
||||
private const val PKG_MESSENGER = "com.example.messenger"
|
||||
|
||||
@@ -98,6 +98,7 @@ public interface NotificationLockscreenUserManager {
|
||||
interface UserChangedListener {
|
||||
default void onUserChanged(int userId) {}
|
||||
default void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {}
|
||||
default void onUserRemoved(int userId) {}
|
||||
}
|
||||
|
||||
/** Used to hide notifications on the lockscreen */
|
||||
|
||||
@@ -152,6 +152,15 @@ public class NotificationLockscreenUserManagerImpl implements
|
||||
listener.onUserChanged(mCurrentUserId);
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_USER_REMOVED:
|
||||
int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
|
||||
if (removedUserId != -1) {
|
||||
for (UserChangedListener listener : mListeners) {
|
||||
listener.onUserRemoved(removedUserId);
|
||||
}
|
||||
}
|
||||
updateCurrentProfilesCache();
|
||||
break;
|
||||
case Intent.ACTION_USER_ADDED:
|
||||
case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
|
||||
case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
|
||||
@@ -303,6 +312,7 @@ public class NotificationLockscreenUserManagerImpl implements
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_USER_SWITCHED);
|
||||
filter.addAction(Intent.ACTION_USER_ADDED);
|
||||
filter.addAction(Intent.ACTION_USER_REMOVED);
|
||||
filter.addAction(Intent.ACTION_USER_UNLOCKED);
|
||||
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
|
||||
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
|
||||
|
||||
@@ -271,6 +271,11 @@ public class BubblesManager implements Dumpable {
|
||||
mBubbles.onCurrentProfilesChanged(currentProfiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserRemoved(int userId) {
|
||||
mBubbles.onUserRemoved(userId);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
mSysuiProxy = new Bubbles.SysuiProxy() {
|
||||
|
||||
@@ -66,6 +66,7 @@ import android.hardware.face.FaceManager;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.service.dreams.IDreamManager;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
@@ -365,6 +366,7 @@ public class BubblesTest extends SysuiTestCase {
|
||||
mStatusBarService,
|
||||
mWindowManager,
|
||||
mWindowManagerShellWrapper,
|
||||
mock(UserManager.class),
|
||||
mLauncherApps,
|
||||
mBubbleLogger,
|
||||
mTaskStackListener,
|
||||
|
||||
@@ -55,6 +55,7 @@ import android.hardware.display.AmbientDisplayConfiguration;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.service.dreams.IDreamManager;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
@@ -330,6 +331,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
|
||||
mStatusBarService,
|
||||
mWindowManager,
|
||||
mWindowManagerShellWrapper,
|
||||
mock(UserManager.class),
|
||||
mLauncherApps,
|
||||
mBubbleLogger,
|
||||
mTaskStackListener,
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.wmshell;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.os.Handler;
|
||||
import android.os.UserManager;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
@@ -53,6 +54,7 @@ public class TestableBubbleController extends BubbleController {
|
||||
IStatusBarService statusBarService,
|
||||
WindowManager windowManager,
|
||||
WindowManagerShellWrapper windowManagerShellWrapper,
|
||||
UserManager userManager,
|
||||
LauncherApps launcherApps,
|
||||
BubbleLogger bubbleLogger,
|
||||
TaskStackListenerImpl taskStackListener,
|
||||
@@ -66,10 +68,10 @@ public class TestableBubbleController extends BubbleController {
|
||||
TaskViewTransitions taskViewTransitions,
|
||||
SyncTransactionQueue syncQueue) {
|
||||
super(context, data, Runnable::run, floatingContentCoordinator, dataRepository,
|
||||
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
|
||||
bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
|
||||
oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
|
||||
new SyncExecutor(), taskViewTransitions, syncQueue);
|
||||
statusBarService, windowManager, windowManagerShellWrapper, userManager,
|
||||
launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, positioner,
|
||||
displayController, oneHandedOptional, dragAndDropController, shellMainExecutor,
|
||||
shellMainHandler, new SyncExecutor(), taskViewTransitions, syncQueue);
|
||||
setInflateSynchronously(true);
|
||||
initialize();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user