Send deleteIntent when a bubble is dismissed

Test: atest BubbleControllerTest
Bug: 124381186
Change-Id: Ie332dc394c25a143e75b3f9caa46de6386b392db
This commit is contained in:
Mark Renouf
2019-03-07 13:01:59 -05:00
parent 1532ac0e89
commit 08bc42a06e
6 changed files with 135 additions and 36 deletions

View File

@@ -23,6 +23,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -42,6 +44,7 @@ import android.view.IPinnedStackListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.IntDef;
import androidx.annotation.MainThread;
import com.android.internal.annotations.VisibleForTesting;
@@ -59,6 +62,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import java.lang.annotation.Retention;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -70,10 +75,22 @@ import javax.inject.Singleton;
*/
@Singleton
public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListener {
private static final int MAX_BUBBLES = 5; // TODO: actually enforce this
private static final String TAG = "BubbleController";
private static final int MAX_BUBBLES = 5; // TODO: actually enforce this
@Retention(SOURCE)
@IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION})
@interface DismissReason {}
static final int DISMISS_USER_GESTURE = 1;
static final int DISMISS_AGED = 2;
static final int DISMISS_TASK_FINISHED = 3;
static final int DISMISS_BLOCKED = 4;
static final int DISMISS_NOTIF_CANCEL = 5;
static final int DISMISS_ACCESSIBILITY_ACTION = 6;
// Enables some subset of notifs to automatically become bubbles
private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
@@ -248,11 +265,11 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe
/**
* Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
*/
void dismissStack() {
void dismissStack(@DismissReason int reason) {
if (mStackView == null) {
return;
}
mStackView.stackDismissed();
mStackView.stackDismissed(reason);
updateVisibility();
mNotificationEntryManager.updateNotifications();
@@ -304,9 +321,9 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe
* Must be called from the main thread.
*/
@MainThread
void removeBubble(String key) {
void removeBubble(String key, int reason) {
if (mStackView != null) {
mStackView.removeBubble(key);
mStackView.removeBubble(key, reason);
}
mNotificationEntryManager.updateNotifications();
updateVisibility();
@@ -320,7 +337,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe
boolean samePackage = entry.notification.getPackageName().equals(
e.notification.getPackageName());
if (samePackage) {
removeBubble(entry.key);
removeBubble(entry.key, DISMISS_BLOCKED);
}
}
}
@@ -377,7 +394,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe
}
if (!removedByUser) {
// This was a cancel so we should remove the bubble
removeBubble(entry.key);
removeBubble(entry.key, DISMISS_NOTIF_CANCEL);
}
}
};

View File

@@ -135,7 +135,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
public void onTaskRemovalStarted(int taskId) {
if (mEntry != null) {
// Must post because this is called from a binder thread.
post(() -> mBubbleController.removeBubble(mEntry.key));
post(() -> mBubbleController.removeBubble(mEntry.key,
BubbleController.DISMISS_TASK_FINISHED));
}
}
};

View File

@@ -19,6 +19,8 @@ package com.android.systemui.bubbles;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Outline;
@@ -49,6 +51,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController.DismissReason;
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
@@ -62,6 +65,7 @@ import java.math.RoundingMode;
*/
public class BubbleStackView extends FrameLayout {
private static final String TAG = "BubbleStackView";
private static final boolean DEBUG = false;
/**
* Friction applied to fling animations. Since the stack must land on one of the sides of the
@@ -252,7 +256,7 @@ public class BubbleStackView extends FrameLayout {
}
switch (action) {
case AccessibilityNodeInfo.ACTION_DISMISS:
stackDismissed();
stackDismissed(BubbleController.DISMISS_ACCESSIBILITY_ACTION);
return true;
case AccessibilityNodeInfo.ACTION_COLLAPSE:
collapseStack();
@@ -376,18 +380,12 @@ public class BubbleStackView extends FrameLayout {
/**
* Remove a bubble from the stack.
*/
public void removeBubble(String key) {
public void removeBubble(String key, int reason) {
Bubble b = mBubbleData.removeBubble(key);
if (b == null) {
return;
}
b.entry.setBubbleDismissed(true);
// Remove it from the views
int removedIndex = mBubbleContainer.indexOfChild(b.iconView);
b.expandedView.cleanUpExpandedState();
mBubbleContainer.removeView(b.iconView);
int removedIndex = dismissBubble(b, reason);
int bubbleCount = mBubbleContainer.getChildCount();
if (bubbleCount == 0) {
// If no bubbles remain, collapse the entire stack.
@@ -405,25 +403,62 @@ public class BubbleStackView extends FrameLayout {
mExpandedBubble = null;
}
}
// TODO: consider logging reason code
logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
}
/**
* Dismiss the stack of bubbles.
*/
public void stackDismissed() {
public void stackDismissed(int reason) {
for (Bubble bubble : mBubbleData.getBubbles()) {
bubble.entry.setBubbleDismissed(true);
bubble.expandedView.cleanUpExpandedState();
dismissBubble(bubble, reason);
}
mBubbleData.clear();
collapseStack();
mBubbleContainer.removeAllViews();
mExpandedViewContainer.removeAllViews();
// TODO: consider logging reason code
logBubbleEvent(null /* no bubble associated with bubble stack dismiss */,
StatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED);
}
/**
* Marks the notification entry as dismissed, cleans up Bubble icon and expanded view UI
* elements and calls deleteIntent if necessary.
*
* <p>Note: This does not remove the Bubble from BubbleData.
*
* @param bubble the Bubble being dismissed
* @param reason code for the reason the dismiss was triggered
* @see BubbleController.DismissReason
*/
private int dismissBubble(Bubble bubble, @DismissReason int reason) {
if (DEBUG) {
Log.d(TAG, "dismissBubble: " + bubble + " reason=" + reason);
}
bubble.entry.setBubbleDismissed(true);
bubble.expandedView.cleanUpExpandedState();
// Remove it from the views
int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView);
mBubbleContainer.removeViewAt(removedIndex);
if (reason == BubbleController.DISMISS_USER_GESTURE) {
Notification.BubbleMetadata bubbleMetadata = bubble.entry.getBubbleMetadata();
PendingIntent deleteIntent = bubbleMetadata.getDeleteIntent();
if (deleteIntent != null) {
try {
deleteIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed to send delete intent for bubble with key: "
+ (bubble.entry != null ? bubble.entry.key : " null entry"));
}
}
}
return removedIndex;
}
/**
* Updates a bubble in the stack.
*

View File

@@ -140,7 +140,7 @@ class BubbleTouchHandler implements View.OnTouchListener {
case MotionEvent.ACTION_UP:
trackMovement(event);
if (mInDismissTarget && isStack) {
mController.dismissStack();
mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
} else if (mMovedEnough) {
mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
final float velX = mVelocityTracker.getXVelocity();
@@ -152,7 +152,8 @@ class BubbleTouchHandler implements View.OnTouchListener {
mStack.onBubbleDragFinish(
mTouchedView, viewX, viewY, velX, velY, /* dismissed */ dismissed);
if (dismissed) {
mController.removeBubble(((BubbleView) mTouchedView).getKey());
mController.removeBubble(((BubbleView) mTouchedView).getKey(),
BubbleController.DISMISS_USER_GESTURE);
}
}
} else if (mTouchedView == mStack.getExpandedBubbleView()) {

View File

@@ -20,10 +20,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.IActivityManager;
import android.app.PendingIntent;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -83,6 +86,9 @@ public class BubbleControllerTest extends SysuiTestCase {
@Mock
private BubbleController.BubbleExpandListener mBubbleExpandListener;
@Mock
private PendingIntent mDeleteIntent;
private BubbleData mBubbleData;
@Before
@@ -98,9 +104,9 @@ public class BubbleControllerTest extends SysuiTestCase {
// Need notifications for bubbles
mNotificationTestHelper = new NotificationTestHelper(mContext);
mRow = mNotificationTestHelper.createBubble();
mRow2 = mNotificationTestHelper.createBubble();
mNoChannelRow = mNotificationTestHelper.createBubble();
mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
mNoChannelRow = mNotificationTestHelper.createBubble(mDeleteIntent);
// Return non-null notification data from the NEM
when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
@@ -141,11 +147,10 @@ public class BubbleControllerTest extends SysuiTestCase {
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
mBubbleController.removeBubble(mRow.getEntry().key);
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
assertFalse(mStatusBarWindowController.getBubblesShowing());
assertTrue(mRow.getEntry().isBubbleDismissed());
verify(mNotificationEntryManager).updateNotifications();
verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
}
@@ -155,7 +160,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
assertTrue(mBubbleController.hasBubbles());
mBubbleController.dismissStack();
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
assertFalse(mStatusBarWindowController.getBubblesShowing());
verify(mNotificationEntryManager).updateNotifications();
assertTrue(mRow.getEntry().isBubbleDismissed());
@@ -271,7 +276,8 @@ public class BubbleControllerTest extends SysuiTestCase {
assertFalse(mRow2.getEntry().showInShadeWhenBubble());
// Dismiss currently expanded
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey());
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
BubbleController.DISMISS_USER_GESTURE);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
// Make sure next bubble is selected
@@ -279,7 +285,8 @@ public class BubbleControllerTest extends SysuiTestCase {
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
// Dismiss that one
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey());
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
@@ -299,6 +306,28 @@ public class BubbleControllerTest extends SysuiTestCase {
assertTrue(mRow.getEntry().showInShadeWhenBubble());
}
@Test
public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_AGED);
verify(mDeleteIntent, never()).send();
}
@Test
public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(1)).send();
}
@Test
public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(2)).send();
}
static class TestableBubbleController extends BubbleController {
TestableBubbleController(Context context,

View File

@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.Notification;
import android.app.Notification.BubbleMetadata;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Context;
@@ -152,8 +153,18 @@ public class NotificationTestHelper {
* Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
*/
public ExpandableNotificationRow createBubble() throws Exception {
return createBubble(null);
}
/**
* Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
*
* @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent}
*/
public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent)
throws Exception {
Notification n = createNotification(false /* isGroupSummary */,
null /* groupKey */, true /* isBubble */);
null /* groupKey */, true /* isBubble */, deleteIntent);
return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
}
@@ -196,7 +207,8 @@ public class NotificationTestHelper {
* @return a notification that is in the group specified or standalone if unspecified
*/
private Notification createNotification(boolean isGroupSummary, @Nullable String groupKey) {
return createNotification(isGroupSummary, groupKey, false /* isBubble */);
return createNotification(isGroupSummary, groupKey, false /* isBubble */,
null /* bubbleDeleteIntent */);
}
/**
@@ -208,7 +220,8 @@ public class NotificationTestHelper {
* @return a notification that is in the group specified or standalone if unspecified
*/
private Notification createNotification(boolean isGroupSummary,
@Nullable String groupKey, boolean isBubble) {
@Nullable String groupKey, boolean isBubble,
@Nullable PendingIntent bubbleDeleteIntent) {
Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
R.drawable.ic_person)
.setCustomContentView(new RemoteViews(mContext.getPackageName(),
@@ -227,7 +240,8 @@ public class NotificationTestHelper {
notificationBuilder.setGroup(groupKey);
}
if (isBubble) {
notificationBuilder.setBubbleMetadata(makeBubbleMetadata());
BubbleMetadata metadata = makeBubbleMetadata(bubbleDeleteIntent);
notificationBuilder.setBubbleMetadata(metadata);
}
return notificationBuilder.build();
}
@@ -291,11 +305,13 @@ public class NotificationTestHelper {
return row;
}
private Notification.BubbleMetadata makeBubbleMetadata() {
private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) {
Intent target = new Intent(mContext, BubblesTestActivity.class);
PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
return new Notification.BubbleMetadata.Builder()
return new BubbleMetadata.Builder()
.setIntent(bubbleIntent)
.setDeleteIntent(deleteIntent)
.setTitle("bubble title")
.setIcon(Icon.createWithResource(mContext, 1))
.setDesiredHeight(314)