Merge "Remove bubbles if their shortcuts are removed or if the app is uninstalled." into rvc-dev am: 803d1971a7 am: d0370df46f am: d8b558680d
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11934273 Change-Id: Ib2af309557d2e951f322299c32396e80aa75ab18
This commit is contained in:
@@ -52,13 +52,16 @@ import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.NotificationListenerService.RankingMap;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
@@ -130,7 +133,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
@IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
|
||||
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
|
||||
DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
|
||||
DISMISS_OVERFLOW_MAX_REACHED})
|
||||
DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED})
|
||||
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
|
||||
@interface DismissReason {}
|
||||
|
||||
@@ -145,6 +148,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
static final int DISMISS_GROUP_CANCELLED = 9;
|
||||
static final int DISMISS_INVALID_INTENT = 10;
|
||||
static final int DISMISS_OVERFLOW_MAX_REACHED = 11;
|
||||
static final int DISMISS_SHORTCUT_REMOVED = 12;
|
||||
static final int DISMISS_PACKAGE_REMOVED = 13;
|
||||
|
||||
private final Context mContext;
|
||||
private final NotificationEntryManager mNotificationEntryManager;
|
||||
@@ -334,7 +339,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
SysUiState sysUiState,
|
||||
INotificationManager notificationManager,
|
||||
@Nullable IStatusBarService statusBarService,
|
||||
WindowManager windowManager) {
|
||||
WindowManager windowManager,
|
||||
LauncherApps launcherApps) {
|
||||
dumpManager.registerDumpable(TAG, this);
|
||||
mContext = context;
|
||||
mShadeController = shadeController;
|
||||
@@ -426,6 +432,47 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
});
|
||||
|
||||
mBubbleIconFactory = new BubbleIconFactory(context);
|
||||
|
||||
launcherApps.registerCallback(new LauncherApps.Callback() {
|
||||
@Override
|
||||
public void onPackageAdded(String s, UserHandle userHandle) {}
|
||||
|
||||
@Override
|
||||
public void onPackageChanged(String s, UserHandle userHandle) {}
|
||||
|
||||
@Override
|
||||
public void onPackageRemoved(String s, UserHandle userHandle) {
|
||||
// Remove bubbles with this package name, since it has been uninstalled and attempts
|
||||
// to open a bubble from an uninstalled app can cause issues.
|
||||
mBubbleData.removeBubblesWithPackageName(s, DISMISS_PACKAGE_REMOVED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackagesAvailable(String[] strings, UserHandle userHandle,
|
||||
boolean b) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackagesUnavailable(String[] packages, UserHandle userHandle,
|
||||
boolean b) {
|
||||
for (String packageName : packages) {
|
||||
// Remove bubbles from unavailable apps. This can occur when the app is on
|
||||
// external storage that has been removed.
|
||||
mBubbleData.removeBubblesWithPackageName(packageName, DISMISS_PACKAGE_REMOVED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShortcutsChanged(String packageName, List<ShortcutInfo> validShortcuts,
|
||||
UserHandle user) {
|
||||
super.onShortcutsChanged(packageName, validShortcuts, user);
|
||||
|
||||
// Remove bubbles whose shortcuts aren't in the latest list of valid shortcuts.
|
||||
mBubbleData.removeBubblesWithInvalidShortcuts(
|
||||
packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1102,7 +1149,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
@MainThread
|
||||
void removeBubble(String key, int reason) {
|
||||
if (mBubbleData.hasAnyBubbleWithKey(key)) {
|
||||
mBubbleData.notificationEntryRemoved(key, reason);
|
||||
mBubbleData.dismissBubbleWithKey(key, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,7 +1207,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
rankingMap.getRanking(key, mTmpRanking);
|
||||
boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
|
||||
if (isActiveBubble && !mTmpRanking.canBubble()) {
|
||||
mBubbleData.notificationEntryRemoved(entry.getKey(),
|
||||
mBubbleData.dismissBubbleWithKey(entry.getKey(),
|
||||
BubbleController.DISMISS_BLOCKED);
|
||||
} else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
|
||||
entry.setFlagBubble(true);
|
||||
|
||||
@@ -24,6 +24,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
|
||||
import android.annotation.NonNull;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
@@ -42,8 +43,12 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -286,9 +291,9 @@ public class BubbleData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a notification associated with a bubble is removed.
|
||||
* Dismisses the bubble with the matching key, if it exists.
|
||||
*/
|
||||
public void notificationEntryRemoved(String key, @DismissReason int reason) {
|
||||
public void dismissBubbleWithKey(String key, @DismissReason int reason) {
|
||||
if (DEBUG_BUBBLE_DATA) {
|
||||
Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
|
||||
}
|
||||
@@ -349,6 +354,44 @@ public class BubbleData {
|
||||
return bubbleChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes bubbles from the given package whose shortcut are not in the provided list of valid
|
||||
* shortcuts.
|
||||
*/
|
||||
public void removeBubblesWithInvalidShortcuts(
|
||||
String packageName, List<ShortcutInfo> validShortcuts, int reason) {
|
||||
|
||||
final Set<String> validShortcutIds = new HashSet<String>();
|
||||
for (ShortcutInfo info : validShortcuts) {
|
||||
validShortcutIds.add(info.getId());
|
||||
}
|
||||
|
||||
final Predicate<Bubble> invalidBubblesFromPackage = bubble ->
|
||||
packageName.equals(bubble.getPackageName())
|
||||
&& (bubble.getShortcutInfo() == null
|
||||
|| !bubble.getShortcutInfo().isEnabled()
|
||||
|| !validShortcutIds.contains(bubble.getShortcutInfo().getId()));
|
||||
|
||||
final Consumer<Bubble> removeBubble = bubble ->
|
||||
dismissBubbleWithKey(bubble.getKey(), reason);
|
||||
|
||||
performActionOnBubblesMatching(getBubbles(), invalidBubblesFromPackage, removeBubble);
|
||||
performActionOnBubblesMatching(
|
||||
getOverflowBubbles(), invalidBubblesFromPackage, removeBubble);
|
||||
}
|
||||
|
||||
/** Dismisses all bubbles from the given package. */
|
||||
public void removeBubblesWithPackageName(String packageName, int reason) {
|
||||
final Predicate<Bubble> bubbleMatchesPackage = bubble ->
|
||||
bubble.getPackageName().equals(packageName);
|
||||
|
||||
final Consumer<Bubble> removeBubble = bubble ->
|
||||
dismissBubbleWithKey(bubble.getKey(), reason);
|
||||
|
||||
performActionOnBubblesMatching(getBubbles(), bubbleMatchesPackage, removeBubble);
|
||||
performActionOnBubblesMatching(getOverflowBubbles(), bubbleMatchesPackage, removeBubble);
|
||||
}
|
||||
|
||||
private void doAdd(Bubble bubble) {
|
||||
if (DEBUG_BUBBLE_DATA) {
|
||||
Log.d(TAG, "doAdd: " + bubble);
|
||||
@@ -388,6 +431,21 @@ public class BubbleData {
|
||||
}
|
||||
}
|
||||
|
||||
/** Runs the given action on Bubbles that match the given predicate. */
|
||||
private void performActionOnBubblesMatching(
|
||||
List<Bubble> bubbles, Predicate<Bubble> predicate, Consumer<Bubble> action) {
|
||||
final List<Bubble> matchingBubbles = new ArrayList<>();
|
||||
for (Bubble bubble : bubbles) {
|
||||
if (predicate.test(bubble)) {
|
||||
matchingBubbles.add(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
for (Bubble matchingBubble : matchingBubbles) {
|
||||
action.accept(matchingBubble);
|
||||
}
|
||||
}
|
||||
|
||||
private void doRemove(String key, @DismissReason int reason) {
|
||||
if (DEBUG_BUBBLE_DATA) {
|
||||
Log.d(TAG, "doRemove: " + key);
|
||||
@@ -400,9 +458,11 @@ public class BubbleData {
|
||||
if (indexToRemove == -1) {
|
||||
if (hasOverflowBubbleWithKey(key)
|
||||
&& (reason == BubbleController.DISMISS_NOTIF_CANCEL
|
||||
|| reason == BubbleController.DISMISS_GROUP_CANCELLED
|
||||
|| reason == BubbleController.DISMISS_NO_LONGER_BUBBLE
|
||||
|| reason == BubbleController.DISMISS_BLOCKED)) {
|
||||
|| reason == BubbleController.DISMISS_GROUP_CANCELLED
|
||||
|| reason == BubbleController.DISMISS_NO_LONGER_BUBBLE
|
||||
|| reason == BubbleController.DISMISS_BLOCKED
|
||||
|| reason == BubbleController.DISMISS_SHORTCUT_REMOVED
|
||||
|| reason == BubbleController.DISMISS_PACKAGE_REMOVED)) {
|
||||
|
||||
Bubble b = getOverflowBubbleWithKey(key);
|
||||
if (DEBUG_BUBBLE_DATA) {
|
||||
|
||||
@@ -2232,7 +2232,7 @@ public class BubbleStackView extends FrameLayout
|
||||
|
||||
private void dismissBubbleIfExists(@Nullable Bubble bubble) {
|
||||
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
bubble.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.bubbles.dagger;
|
||||
|
||||
import android.app.INotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
@@ -72,7 +73,8 @@ public interface BubbleModule {
|
||||
SysUiState sysUiState,
|
||||
INotificationManager notifManager,
|
||||
IStatusBarService statusBarService,
|
||||
WindowManager windowManager) {
|
||||
WindowManager windowManager,
|
||||
LauncherApps launcherApps) {
|
||||
return new BubbleController(
|
||||
context,
|
||||
notificationShadeWindowController,
|
||||
@@ -94,6 +96,7 @@ public interface BubbleModule {
|
||||
sysUiState,
|
||||
notifManager,
|
||||
statusBarService,
|
||||
windowManager);
|
||||
windowManager,
|
||||
launcherApps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import android.app.IActivityManager;
|
||||
import android.app.INotificationManager;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.hardware.display.AmbientDisplayConfiguration;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.os.Handler;
|
||||
@@ -179,6 +180,8 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
private NotificationShadeWindowView mNotificationShadeWindowView;
|
||||
@Mock
|
||||
private IStatusBarService mStatusBarService;
|
||||
@Mock
|
||||
private LauncherApps mLauncherApps;
|
||||
|
||||
private BubbleData mBubbleData;
|
||||
|
||||
@@ -256,7 +259,8 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
mSysUiState,
|
||||
mock(INotificationManager.class),
|
||||
mStatusBarService,
|
||||
mWindowManager);
|
||||
mWindowManager,
|
||||
mLauncherApps);
|
||||
mBubbleController.setExpandListener(mBubbleExpandListener);
|
||||
|
||||
// Get a reference to the BubbleController's entry listener
|
||||
|
||||
@@ -177,7 +177,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
|
||||
// Verify
|
||||
@@ -300,13 +300,13 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
mBubbleData.setMaxOverflowBubbles(1);
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
|
||||
|
||||
// Overflow max of 1 is reached; A1 is oldest, so it gets removed
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
|
||||
@@ -328,13 +328,13 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(mEntryA1.getKey(),
|
||||
mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(),
|
||||
BubbleController.DISMISS_NOTIF_CANCEL);
|
||||
verifyUpdateReceived();
|
||||
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(mEntryA2.getKey(),
|
||||
mBubbleData.dismissBubbleWithKey(mEntryA2.getKey(),
|
||||
BubbleController.DISMISS_GROUP_CANCELLED);
|
||||
verifyUpdateReceived();
|
||||
assertOverflowChangedTo(ImmutableList.of());
|
||||
@@ -415,7 +415,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
// TODO: this should fail if things work as I expect them to?
|
||||
@@ -436,7 +436,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertOrderNotChanged();
|
||||
@@ -456,7 +456,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA2.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
|
||||
verifyUpdateReceived();
|
||||
assertSelectionChangedTo(mBubbleB2);
|
||||
@@ -531,7 +531,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
|
||||
// Verify the selection was cleared.
|
||||
@@ -632,7 +632,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryB2.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleA1);
|
||||
@@ -657,12 +657,12 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertSelectionChangedTo(mBubbleB1);
|
||||
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryB1.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertSelectionChangedTo(mBubbleA1);
|
||||
@@ -777,7 +777,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
mBubbleData.setListener(mListener);
|
||||
|
||||
// Test
|
||||
mBubbleData.notificationEntryRemoved(
|
||||
mBubbleData.dismissBubbleWithKey(
|
||||
mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
|
||||
verifyUpdateReceived();
|
||||
assertExpandedChangedTo(false);
|
||||
|
||||
@@ -40,6 +40,7 @@ import android.app.IActivityManager;
|
||||
import android.app.INotificationManager;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.display.AmbientDisplayConfiguration;
|
||||
import android.hardware.face.FaceManager;
|
||||
@@ -174,6 +175,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
|
||||
private LockscreenLockIconController mLockIconController;
|
||||
@Mock
|
||||
private IStatusBarService mStatusBarService;
|
||||
@Mock
|
||||
private LauncherApps mLauncherApps;
|
||||
|
||||
private BubbleData mBubbleData;
|
||||
|
||||
@@ -241,7 +244,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
|
||||
mSysUiState,
|
||||
mock(INotificationManager.class),
|
||||
mStatusBarService,
|
||||
mWindowManager);
|
||||
mWindowManager,
|
||||
mLauncherApps);
|
||||
mBubbleController.addNotifCallback(mNotifCallback);
|
||||
mBubbleController.setExpandListener(mBubbleExpandListener);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.bubbles;
|
||||
|
||||
import android.app.INotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
@@ -61,14 +62,15 @@ public class TestableBubbleController extends BubbleController {
|
||||
SysUiState sysUiState,
|
||||
INotificationManager notificationManager,
|
||||
IStatusBarService statusBarService,
|
||||
WindowManager windowManager) {
|
||||
WindowManager windowManager,
|
||||
LauncherApps launcherApps) {
|
||||
super(context,
|
||||
notificationShadeWindowController, statusBarStateController, shadeController,
|
||||
data, Runnable::run, configurationController, interruptionStateProvider,
|
||||
zenModeController, lockscreenUserManager, groupManager, entryManager,
|
||||
notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
|
||||
dataRepository, sysUiState, notificationManager, statusBarService,
|
||||
windowManager);
|
||||
windowManager, launcherApps);
|
||||
setInflateSynchronously(true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user