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:
TreeHugger Robot
2020-06-20 00:37:49 +00:00
committed by Automerger Merge Worker
8 changed files with 149 additions and 29 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}