diff options
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); } } |