Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +64 −13 Original line number Diff line number Diff line Loading @@ -70,12 +70,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseSetArray; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; Loading Loading @@ -109,8 +107,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; Loading Loading @@ -177,8 +177,8 @@ public class BubbleController implements ConfigurationChangeListener { private int mCurrentUserId; // Current profiles of the user (e.g. user with a workprofile) private SparseArray<UserInfo> mCurrentProfiles; // Saves notification keys of active bubbles when users are switched. private final SparseSetArray<String> mSavedBubbleKeysPerUser; // Saves data about active bubbles when users are switched. private final SparseArray<UserBubbleData> mSavedUserBubbleData; // Used when ranking updates occur and we check if things should bubble / unbubble private NotificationListenerService.Ranking mTmpRanking; Loading Loading @@ -271,7 +271,7 @@ public class BubbleController implements ConfigurationChangeListener { mCurrentUserId = ActivityManager.getCurrentUser(); mBubblePositioner = positioner; mBubbleData = data; mSavedBubbleKeysPerUser = new SparseSetArray<>(); mSavedUserBubbleData = new SparseArray<>(); mBubbleIconFactory = new BubbleIconFactory(context); mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context); mDisplayController = displayController; Loading Loading @@ -420,6 +420,13 @@ public class BubbleController implements ConfigurationChangeListener { List<UserInfo> users = mUserManager.getAliveUsers(); mDataRepository.sanitizeBubbles(users); // Init profiles SparseArray<UserInfo> userProfiles = new SparseArray<>(); for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { userProfiles.put(user.id, user); } mCurrentProfiles = userProfiles; mShellController.addConfigurationChangeListener(this); } Loading Loading @@ -774,11 +781,13 @@ public class BubbleController implements ConfigurationChangeListener { */ private void saveBubbles(@UserIdInt int userId) { // First clear any existing keys that might be stored. mSavedBubbleKeysPerUser.remove(userId); mSavedUserBubbleData.remove(userId); UserBubbleData userBubbleData = new UserBubbleData(); // Add in all active bubbles for the current user. for (Bubble bubble : mBubbleData.getBubbles()) { mSavedBubbleKeysPerUser.add(userId, bubble.getKey()); userBubbleData.add(bubble.getKey(), bubble.showInShade()); } mSavedUserBubbleData.put(userId, userBubbleData); } /** Loading @@ -787,22 +796,23 @@ public class BubbleController implements ConfigurationChangeListener { * @param userId the id of the user */ private void restoreBubbles(@UserIdInt int userId) { ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId); if (savedBubbleKeys == null) { UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId); if (savedBubbleData == null) { // There were no bubbles saved for this used. return; } mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> { mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> { mMainExecutor.execute(() -> { for (BubbleEntry e : entries) { if (canLaunchInTaskView(mContext, e)) { updateBubble(e, true /* suppressFlyout */, false /* showInShade */); boolean showInShade = savedBubbleData.isShownInShade(e.getKey()); updateBubble(e, true /* suppressFlyout */, showInShade); } } }); }); // Finally, remove the entries for this user now that bubbles are restored. mSavedBubbleKeysPerUser.remove(userId); mSavedUserBubbleData.remove(userId); } @Override Loading Loading @@ -993,7 +1003,19 @@ public class BubbleController implements ConfigurationChangeListener { */ @VisibleForTesting public void updateBubble(BubbleEntry notif) { int bubbleUserId = notif.getStatusBarNotification().getUserId(); if (isCurrentProfile(bubbleUserId)) { updateBubble(notif, false /* suppressFlyout */, true /* showInShade */); } else { // Skip update, but store it in user bubbles so it gets restored after user switch mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(), true /* shownInShade */); if (DEBUG_BUBBLE_CONTROLLER) { Log.d(TAG, "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId + " current userId=" + mCurrentUserId); } } } /** Loading Loading @@ -1842,4 +1864,33 @@ public class BubbleController implements ConfigurationChangeListener { } } } /** * Bubble data that is stored per user. * Used to store and restore active bubbles during user switching. */ private static class UserBubbleData { private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>(); /** * Add bubble key and whether it should be shown in notification shade */ void add(String key, boolean shownInShade) { mKeyToShownInShadeMap.put(key, shownInShade); } /** * Get all bubble keys stored for this user */ Set<String> getKeys() { return mKeyToShownInShadeMap.keySet(); } /** * Check if this bubble with the given key should be shown in the notification shade */ boolean isShownInShade(String key) { return mKeyToShownInShadeMap.get(key); } } } libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +2 −3 Original line number Diff line number Diff line Loading @@ -23,12 +23,10 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Bundle; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; Loading @@ -42,6 +40,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; Loading Loading @@ -284,7 +283,7 @@ public interface Bubbles { void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback); void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys, void getShouldRestoredEntries(Set<String> savedBubbleKeys, Consumer<List<BubbleEntry>> callback); void setNotificationInterruption(String key); Loading packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +2 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.ZenModeConfig; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; Loading Loading @@ -83,6 +82,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; Loading Loading @@ -262,7 +262,7 @@ public class BubblesManager implements Dumpable { } @Override public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys, public void getShouldRestoredEntries(Set<String> savedBubbleKeys, Consumer<List<BubbleEntry>> callback) { sysuiMainExecutor.execute(() -> { List<BubbleEntry> result = new ArrayList<>(); Loading packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +54 −2 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; Loading Loading @@ -143,7 +144,10 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Optional; Loading Loading @@ -252,6 +256,8 @@ public class BubblesTest extends SysuiTestCase { private TaskViewTransitions mTaskViewTransitions; @Mock private Optional<OneHandedController> mOneHandedOptional; @Mock private UserManager mUserManager; private TestableBubblePositioner mPositioner; Loading Loading @@ -314,6 +320,9 @@ public class BubblesTest extends SysuiTestCase { mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn( Collections.singletonList(mock(UserInfo.class))); TestableNotificationInterruptStateProviderImpl interruptionStateProvider = new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), mock(PowerManager.class), Loading @@ -339,7 +348,7 @@ public class BubblesTest extends SysuiTestCase { mStatusBarService, mWindowManager, mWindowManagerShellWrapper, mock(UserManager.class), mUserManager, mLauncherApps, mBubbleLogger, mTaskStackListener, Loading Loading @@ -1025,7 +1034,7 @@ public class BubblesTest extends SysuiTestCase { assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull(); // Switch users mBubbleController.onUserChanged(secondUserId); switchUser(secondUserId); assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); // Give this user some bubbles Loading @@ -1042,6 +1051,41 @@ public class BubblesTest extends SysuiTestCase { verify(mDataRepository, times(2)).loadBubbles(anyInt(), any()); } @Test public void testOnUserChanged_bubblesRestored() { int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(); int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier(); // Mock current profile when(mLockscreenUserManager.isCurrentProfile(firstUserId)).thenReturn(true); when(mLockscreenUserManager.isCurrentProfile(secondUserId)).thenReturn(false); mBubbleController.updateBubble(mBubbleEntry); assertThat(mBubbleController.hasBubbles()).isTrue(); // We start with 1 bubble assertThat(mBubbleData.getBubbles()).hasSize(1); // Switch to second user switchUser(secondUserId); // Second user has no bubbles assertThat(mBubbleController.hasBubbles()).isFalse(); // Send bubble update for first user, ensure it does not show up mBubbleController.updateBubble(mBubbleEntry2); assertThat(mBubbleController.hasBubbles()).isFalse(); // Start returning notif for first user again when(mCommonNotifCollection.getAllNotifs()).thenReturn(Arrays.asList(mRow, mRow2)); // Switch back to first user switchUser(firstUserId); // Check we now have two bubbles, one previous and one new that came in assertThat(mBubbleController.hasBubbles()).isTrue(); // Now there are 2 bubbles assertThat(mBubbleData.getBubbles()).hasSize(2); } /** * Verifies we only load the overflow data once. */ Loading Loading @@ -1443,6 +1487,14 @@ public class BubblesTest extends SysuiTestCase { .build(); } private void switchUser(int userId) { when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenAnswer( (Answer<Boolean>) invocation -> invocation.<Integer>getArgument(0) == userId); SparseArray<UserInfo> userInfos = new SparseArray<>(1); userInfos.put(userId, mock(UserInfo.class)); mBubbleController.onCurrentProfilesChanged(userInfos); mBubbleController.onUserChanged(userId); } /** * Asserts that the bubble stack is expanded and also validates the cached state is updated. Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +64 −13 Original line number Diff line number Diff line Loading @@ -70,12 +70,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseSetArray; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; Loading Loading @@ -109,8 +107,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; Loading Loading @@ -177,8 +177,8 @@ public class BubbleController implements ConfigurationChangeListener { private int mCurrentUserId; // Current profiles of the user (e.g. user with a workprofile) private SparseArray<UserInfo> mCurrentProfiles; // Saves notification keys of active bubbles when users are switched. private final SparseSetArray<String> mSavedBubbleKeysPerUser; // Saves data about active bubbles when users are switched. private final SparseArray<UserBubbleData> mSavedUserBubbleData; // Used when ranking updates occur and we check if things should bubble / unbubble private NotificationListenerService.Ranking mTmpRanking; Loading Loading @@ -271,7 +271,7 @@ public class BubbleController implements ConfigurationChangeListener { mCurrentUserId = ActivityManager.getCurrentUser(); mBubblePositioner = positioner; mBubbleData = data; mSavedBubbleKeysPerUser = new SparseSetArray<>(); mSavedUserBubbleData = new SparseArray<>(); mBubbleIconFactory = new BubbleIconFactory(context); mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context); mDisplayController = displayController; Loading Loading @@ -420,6 +420,13 @@ public class BubbleController implements ConfigurationChangeListener { List<UserInfo> users = mUserManager.getAliveUsers(); mDataRepository.sanitizeBubbles(users); // Init profiles SparseArray<UserInfo> userProfiles = new SparseArray<>(); for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { userProfiles.put(user.id, user); } mCurrentProfiles = userProfiles; mShellController.addConfigurationChangeListener(this); } Loading Loading @@ -774,11 +781,13 @@ public class BubbleController implements ConfigurationChangeListener { */ private void saveBubbles(@UserIdInt int userId) { // First clear any existing keys that might be stored. mSavedBubbleKeysPerUser.remove(userId); mSavedUserBubbleData.remove(userId); UserBubbleData userBubbleData = new UserBubbleData(); // Add in all active bubbles for the current user. for (Bubble bubble : mBubbleData.getBubbles()) { mSavedBubbleKeysPerUser.add(userId, bubble.getKey()); userBubbleData.add(bubble.getKey(), bubble.showInShade()); } mSavedUserBubbleData.put(userId, userBubbleData); } /** Loading @@ -787,22 +796,23 @@ public class BubbleController implements ConfigurationChangeListener { * @param userId the id of the user */ private void restoreBubbles(@UserIdInt int userId) { ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId); if (savedBubbleKeys == null) { UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId); if (savedBubbleData == null) { // There were no bubbles saved for this used. return; } mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> { mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> { mMainExecutor.execute(() -> { for (BubbleEntry e : entries) { if (canLaunchInTaskView(mContext, e)) { updateBubble(e, true /* suppressFlyout */, false /* showInShade */); boolean showInShade = savedBubbleData.isShownInShade(e.getKey()); updateBubble(e, true /* suppressFlyout */, showInShade); } } }); }); // Finally, remove the entries for this user now that bubbles are restored. mSavedBubbleKeysPerUser.remove(userId); mSavedUserBubbleData.remove(userId); } @Override Loading Loading @@ -993,7 +1003,19 @@ public class BubbleController implements ConfigurationChangeListener { */ @VisibleForTesting public void updateBubble(BubbleEntry notif) { int bubbleUserId = notif.getStatusBarNotification().getUserId(); if (isCurrentProfile(bubbleUserId)) { updateBubble(notif, false /* suppressFlyout */, true /* showInShade */); } else { // Skip update, but store it in user bubbles so it gets restored after user switch mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(), true /* shownInShade */); if (DEBUG_BUBBLE_CONTROLLER) { Log.d(TAG, "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId + " current userId=" + mCurrentUserId); } } } /** Loading Loading @@ -1842,4 +1864,33 @@ public class BubbleController implements ConfigurationChangeListener { } } } /** * Bubble data that is stored per user. * Used to store and restore active bubbles during user switching. */ private static class UserBubbleData { private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>(); /** * Add bubble key and whether it should be shown in notification shade */ void add(String key, boolean shownInShade) { mKeyToShownInShadeMap.put(key, shownInShade); } /** * Get all bubble keys stored for this user */ Set<String> getKeys() { return mKeyToShownInShadeMap.keySet(); } /** * Check if this bubble with the given key should be shown in the notification shade */ boolean isShownInShade(String key) { return mKeyToShownInShadeMap.get(key); } } }
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +2 −3 Original line number Diff line number Diff line Loading @@ -23,12 +23,10 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Bundle; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; Loading @@ -42,6 +40,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; Loading Loading @@ -284,7 +283,7 @@ public interface Bubbles { void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback); void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys, void getShouldRestoredEntries(Set<String> savedBubbleKeys, Consumer<List<BubbleEntry>> callback); void setNotificationInterruption(String key); Loading
packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +2 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.ZenModeConfig; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; Loading Loading @@ -83,6 +82,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; Loading Loading @@ -262,7 +262,7 @@ public class BubblesManager implements Dumpable { } @Override public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys, public void getShouldRestoredEntries(Set<String> savedBubbleKeys, Consumer<List<BubbleEntry>> callback) { sysuiMainExecutor.execute(() -> { List<BubbleEntry> result = new ArrayList<>(); Loading
packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +54 −2 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; Loading Loading @@ -143,7 +144,10 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Optional; Loading Loading @@ -252,6 +256,8 @@ public class BubblesTest extends SysuiTestCase { private TaskViewTransitions mTaskViewTransitions; @Mock private Optional<OneHandedController> mOneHandedOptional; @Mock private UserManager mUserManager; private TestableBubblePositioner mPositioner; Loading Loading @@ -314,6 +320,9 @@ public class BubblesTest extends SysuiTestCase { mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn( Collections.singletonList(mock(UserInfo.class))); TestableNotificationInterruptStateProviderImpl interruptionStateProvider = new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), mock(PowerManager.class), Loading @@ -339,7 +348,7 @@ public class BubblesTest extends SysuiTestCase { mStatusBarService, mWindowManager, mWindowManagerShellWrapper, mock(UserManager.class), mUserManager, mLauncherApps, mBubbleLogger, mTaskStackListener, Loading Loading @@ -1025,7 +1034,7 @@ public class BubblesTest extends SysuiTestCase { assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull(); // Switch users mBubbleController.onUserChanged(secondUserId); switchUser(secondUserId); assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); // Give this user some bubbles Loading @@ -1042,6 +1051,41 @@ public class BubblesTest extends SysuiTestCase { verify(mDataRepository, times(2)).loadBubbles(anyInt(), any()); } @Test public void testOnUserChanged_bubblesRestored() { int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(); int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier(); // Mock current profile when(mLockscreenUserManager.isCurrentProfile(firstUserId)).thenReturn(true); when(mLockscreenUserManager.isCurrentProfile(secondUserId)).thenReturn(false); mBubbleController.updateBubble(mBubbleEntry); assertThat(mBubbleController.hasBubbles()).isTrue(); // We start with 1 bubble assertThat(mBubbleData.getBubbles()).hasSize(1); // Switch to second user switchUser(secondUserId); // Second user has no bubbles assertThat(mBubbleController.hasBubbles()).isFalse(); // Send bubble update for first user, ensure it does not show up mBubbleController.updateBubble(mBubbleEntry2); assertThat(mBubbleController.hasBubbles()).isFalse(); // Start returning notif for first user again when(mCommonNotifCollection.getAllNotifs()).thenReturn(Arrays.asList(mRow, mRow2)); // Switch back to first user switchUser(firstUserId); // Check we now have two bubbles, one previous and one new that came in assertThat(mBubbleController.hasBubbles()).isTrue(); // Now there are 2 bubbles assertThat(mBubbleData.getBubbles()).hasSize(2); } /** * Verifies we only load the overflow data once. */ Loading Loading @@ -1443,6 +1487,14 @@ public class BubblesTest extends SysuiTestCase { .build(); } private void switchUser(int userId) { when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenAnswer( (Answer<Boolean>) invocation -> invocation.<Integer>getArgument(0) == userId); SparseArray<UserInfo> userInfos = new SparseArray<>(1); userInfos.put(userId, mock(UserInfo.class)); mBubbleController.onCurrentProfilesChanged(userInfos); mBubbleController.onUserChanged(userId); } /** * Asserts that the bubble stack is expanded and also validates the cached state is updated. Loading