Merge "Ignore notification update for not active user" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7fb9bfccb6
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
@@ -993,7 +1003,19 @@ public class BubbleController implements ConfigurationChangeListener {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void updateBubble(BubbleEntry notif) {
|
||||
updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -252,6 +256,8 @@ public class BubblesTest extends SysuiTestCase {
|
||||
private TaskViewTransitions mTaskViewTransitions;
|
||||
@Mock
|
||||
private Optional<OneHandedController> mOneHandedOptional;
|
||||
@Mock
|
||||
private UserManager mUserManager;
|
||||
|
||||
private TestableBubblePositioner mPositioner;
|
||||
|
||||
@@ -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),
|
||||
@@ -339,7 +348,7 @@ public class BubblesTest extends SysuiTestCase {
|
||||
mStatusBarService,
|
||||
mWindowManager,
|
||||
mWindowManagerShellWrapper,
|
||||
mock(UserManager.class),
|
||||
mUserManager,
|
||||
mLauncherApps,
|
||||
mBubbleLogger,
|
||||
mTaskStackListener,
|
||||
@@ -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
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user