summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java70
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java6
8 files changed, 149 insertions, 29 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 500f991f4200..b3abe9621e48 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -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);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index c8706126c1ad..7020f1cb88eb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -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) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index e97860c71efa..c985b083f9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -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);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 097932e5f447..10d301d0fa93 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -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);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 36398a6fc122..5b46b7fa4d9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 1ca2f02db1bd..ed4e6865e508 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -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);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 1c70db3a548e..52c64cc40e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -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);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index ab49134ee8c0..0a6d0716cb85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -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);
}
}