Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 783f1815 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Send deleteIntent when a bubble is dismissed"

parents 0043d5c2 08bc42a0
Loading
Loading
Loading
Loading
+24 −7
Original line number Diff line number Diff line
@@ -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);
            }
        }
    };
+2 −1
Original line number Diff line number Diff line
@@ -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));
            }
        }
    };
+47 −12
Original line number Diff line number Diff line
@@ -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;

    private Point mDisplaySize;

@@ -232,7 +236,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();
@@ -356,18 +360,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.
@@ -385,25 +383,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.
     *
+3 −2
Original line number Diff line number Diff line
@@ -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()) {
+37 −8
Original line number Diff line number Diff line
@@ -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,
Loading