Merge "Delete BubbleTouchHandler in favor of more traditional touch handling." into rvc-dev am: 8fc6e456b2

Change-Id: Iccddbde2b10380768bbafae3255378c5250717fc
This commit is contained in:
TreeHugger Robot
2020-04-08 22:27:11 +00:00
committed by Automerger Merge Worker
9 changed files with 494 additions and 506 deletions

View File

@@ -718,13 +718,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
return mBubbleData.isExpanded();
}
/**
* Tell the stack of bubbles to expand.
*/
public void expandStack() {
mBubbleData.setExpanded(true);
}
/**
* Tell the stack of bubbles to collapse.
*/
@@ -753,12 +746,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
}
@VisibleForTesting
void selectBubble(String key) {
Bubble bubble = mBubbleData.getBubbleWithKey(key);
mBubbleData.setSelectedBubble(bubble);
}
void promoteBubbleFromOverflow(Bubble bubble) {
bubble.setInflateSynchronously(mInflateSynchronously);
mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
@@ -777,13 +764,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
/**
* Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
*/
void dismissStack(@DismissReason int reason) {
mBubbleData.dismissAll(reason);
}
/**
* Directs a back gesture at the bubble stack. When opened, the current expanded bubble
* is forwarded a back key down/up pair.

View File

@@ -28,6 +28,7 @@ import android.content.Context;
import android.service.notification.NotificationListenerService;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import androidx.annotation.Nullable;
@@ -751,6 +752,7 @@ public class BubbleData {
}
@VisibleForTesting(visibility = PRIVATE)
@Nullable
Bubble getBubbleWithKey(String key) {
for (int i = 0; i < mBubbles.size(); i++) {
Bubble bubble = mBubbles.get(i);
@@ -761,6 +763,17 @@ public class BubbleData {
return null;
}
@Nullable
Bubble getBubbleWithView(View view) {
for (int i = 0; i < mBubbles.size(); i++) {
Bubble bubble = mBubbles.get(i);
if (bubble.getIconView() != null && bubble.getIconView().equals(view)) {
return bubble;
}
}
return null;
}
@VisibleForTesting(visibility = PRIVATE)
Bubble getOverflowBubbleWithKey(String key) {
for (int i = 0; i < mOverflowBubbles.size(); i++) {

View File

@@ -30,6 +30,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
@@ -43,6 +44,7 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
import android.util.Log;
@@ -83,6 +85,7 @@ import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.RelativeTouchListener;
import com.android.systemui.util.animation.PhysicsAnimator;
import com.android.systemui.util.magnetictarget.MagnetizedObject;
@@ -239,7 +242,6 @@ public class BubbleStackView extends FrameLayout {
mExpandedAnimationController.dump(fd, pw, args);
}
private BubbleTouchHandler mTouchHandler;
private BubbleController.BubbleExpandListener mExpandListener;
private SysUiState mSysUiState;
@@ -296,7 +298,7 @@ public class BubbleStackView extends FrameLayout {
@Override
public void setValue(Object o, float v) {
onFlyoutDragged(v);
setFlyoutStateForDragLength(v);
}
};
@@ -336,13 +338,6 @@ public class BubbleStackView extends FrameLayout {
*/
private MagnetizedObject<?> mMagnetizedObject;
/**
* The action to run when the magnetized object is released in the dismiss target.
*
* This will actually perform the dismissal of either the stack or an individual bubble.
*/
private Runnable mReleasedInDismissTargetAction;
/**
* The MagneticTarget instance for our circular dismiss view. This is added to the
* MagnetizedObject instances for the stack and any dragged-out bubbles.
@@ -377,7 +372,7 @@ public class BubbleStackView extends FrameLayout {
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mExpandedAnimationController.dismissDraggedOutBubble(
mExpandedAnimationController.getDraggedOutBubble(),
mReleasedInDismissTargetAction);
BubbleStackView.this::dismissMagnetizedObject);
hideDismissTarget();
}
};
@@ -410,7 +405,7 @@ public class BubbleStackView extends FrameLayout {
mStackAnimationController.implodeStack(
() -> {
resetDesaturationAndDarken();
mReleasedInDismissTargetAction.run();
dismissMagnetizedObject();
}
);
@@ -418,6 +413,197 @@ public class BubbleStackView extends FrameLayout {
}
};
/**
* Click listener set on each bubble view. When collapsed, clicking a bubble expands the stack.
* When expanded, clicking a bubble either expands that bubble, or collapses the stack.
*/
private OnClickListener mBubbleClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
final Bubble clickedBubble = mBubbleData.getBubbleWithView(view);
// If the bubble has since left us, ignore the click.
if (clickedBubble == null) {
return;
}
final boolean clickedBubbleIsCurrentlyExpandedBubble =
clickedBubble.getKey().equals(mExpandedBubble.getKey());
if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) {
if (clickedBubble != mBubbleData.getSelectedBubble()) {
// Select the clicked bubble.
mBubbleData.setSelectedBubble(clickedBubble);
} else {
// If the clicked bubble is the selected bubble (but not the expanded bubble),
// that means overflow was previously expanded. Set the selected bubble
// internally without going through BubbleData (which would ignore it since it's
// already selected).
setSelectedBubble(clickedBubble);
}
} else {
// Otherwise, we either tapped the stack (which means we're collapsed
// and should expand) or the currently selected bubble (we're expanded
// and should collapse).
if (!maybeShowStackUserEducation()) {
mBubbleData.setExpanded(!mBubbleData.isExpanded());
}
}
}
};
/**
* Touch listener set on each bubble view. This enables dragging and dismissing the stack (when
* collapsed), or individual bubbles (when expanded).
*/
private RelativeTouchListener mBubbleTouchListener = new RelativeTouchListener() {
@Override
public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
// If we're expanding or collapsing, consume but ignore all touch events.
if (mIsExpansionAnimating) {
return true;
}
if (mBubbleData.isExpanded()) {
maybeShowManageEducation(false /* show */);
// If we're expanded, tell the animation controller to prepare to drag this bubble,
// dispatching to the individual bubble magnet listener.
mExpandedAnimationController.prepareForBubbleDrag(
v /* bubble */,
mMagneticTarget,
mIndividualBubbleMagnetListener);
// Save the magnetized individual bubble so we can dispatch touch events to it.
mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
} else {
// If we're collapsed, prepare to drag the stack. Cancel active animations, set the
// animation controller, and hide the flyout.
mStackAnimationController.cancelStackPositionAnimations();
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
// Also, save the magnetized stack so we can dispatch touch events to it.
mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
mMagnetizedObject.setMagnetListener(mStackMagnetListener);
}
passEventToMagnetizedObject(ev);
// Bubbles are always interested in all touch events!
return true;
}
@Override
public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy) {
// If we're expanding or collapsing, ignore all touch events.
if (mIsExpansionAnimating) {
return;
}
// Show the dismiss target, if we haven't already.
springInDismissTargetMaybe();
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
// bubble since it's stuck to the target.
if (!passEventToMagnetizedObject(ev)) {
if (mBubbleData.isExpanded()) {
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
hideStackUserEducation(false /* fromExpansion */);
mStackAnimationController.moveStackFromTouch(
viewInitialX + dx, viewInitialY + dy);
}
}
}
@Override
public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy, float velX, float velY) {
// If we're expanding or collapsing, ignore all touch events.
if (mIsExpansionAnimating) {
return;
}
// First, see if the magnetized object consumes the event - if so, the bubble was
// released in the target or flung out of it, and we should ignore the event.
if (!passEventToMagnetizedObject(ev)) {
if (mBubbleData.isExpanded()) {
mExpandedAnimationController.snapBubbleBack(v, velX, velY);
} else {
// Fling the stack to the edge, and save whether or not it's going to end up on
// the left side of the screen.
mStackOnLeftOrWillBe =
mStackAnimationController.flingStackThenSpringToEdge(
viewInitialX + dx, velX, velY) <= 0;
updateBubbleZOrdersAndDotPosition(true /* animate */);
logBubbleEvent(null /* no bubble associated with bubble stack move */,
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
}
hideDismissTarget();
}
}
};
/** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
private OnClickListener mFlyoutClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
if (maybeShowStackUserEducation()) {
// If we're showing user education, don't open the bubble show the education first
mBubbleToExpandAfterFlyoutCollapse = null;
} else {
mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
}
mFlyout.removeCallbacks(mHideFlyout);
mHideFlyout.run();
}
};
/** Touch listener for the flyout. This enables the drag-to-dismiss gesture on the flyout. */
private RelativeTouchListener mFlyoutTouchListener = new RelativeTouchListener() {
@Override
public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
mFlyout.removeCallbacks(mHideFlyout);
return true;
}
@Override
public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy) {
setFlyoutStateForDragLength(dx);
}
@Override
public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy, float velX, float velY) {
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
final boolean metRequiredVelocity =
onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
final boolean metRequiredDeltaX =
onLeft
? dx < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
: dx > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
final boolean shouldDismiss = metRequiredVelocity
|| (metRequiredDeltaX && !isCancelFling);
mFlyout.removeCallbacks(mHideFlyout);
animateFlyoutCollapsed(shouldDismiss, velX);
maybeShowStackUserEducation();
}
};
private ViewGroup mDismissTargetContainer;
private PhysicsAnimator<View> mDismissTargetAnimator;
private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
@@ -436,6 +622,7 @@ public class BubbleStackView extends FrameLayout {
private BubbleManageEducationView mManageEducationView;
private boolean mAnimatingManageEducationAway;
@SuppressLint("ClickableViewAccessibility")
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
@@ -444,8 +631,6 @@ public class BubbleStackView extends FrameLayout {
mBubbleData = data;
mInflater = LayoutInflater.from(context);
mTouchHandler = new BubbleTouchHandler(this, data, context);
setOnTouchListener(mTouchHandler);
mSysUiState = sysUiState;
@@ -641,6 +826,18 @@ public class BubbleStackView extends FrameLayout {
mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
});
// If the stack itself is touched, it means none of its touchable views (bubbles, flyouts,
// ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
setOnTouchListener((view, ev) -> {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
if (mBubbleData.isExpanded()) {
mBubbleData.setExpanded(false);
}
}
return false;
});
}
private void setUpUserEducation() {
@@ -690,6 +887,7 @@ public class BubbleStackView extends FrameLayout {
}
}
@SuppressLint("ClickableViewAccessibility")
private void setUpFlyout() {
if (mFlyout != null) {
removeView(mFlyout);
@@ -699,6 +897,8 @@ public class BubbleStackView extends FrameLayout {
mFlyout.animate()
.setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator());
mFlyout.setOnClickListener(mFlyoutClickListener);
mFlyout.setOnTouchListener(mFlyoutTouchListener);
addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
}
@@ -718,6 +918,7 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow));
}
/**
* Handle theme changes.
@@ -920,6 +1121,7 @@ public class BubbleStackView extends FrameLayout {
}
// via BubbleData.Listener
@SuppressLint("ClickableViewAccessibility")
void addBubble(Bubble bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "addBubble: " + bubble);
@@ -944,6 +1146,9 @@ public class BubbleStackView extends FrameLayout {
bubble.getIconView().setDotPositionOnLeft(
!mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
bubble.getIconView().setOnClickListener(mBubbleClickListener);
bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
@@ -1009,10 +1214,6 @@ public class BubbleStackView extends FrameLayout {
updatePointerPosition();
}
void showOverflow() {
setSelectedBubble(mBubbleOverflow);
}
/**
* Changes the currently selected bubble. If the stack is already expanded, the newly selected
* bubble will be shown immediately. This does not change the expanded state or change the
@@ -1177,14 +1378,6 @@ public class BubbleStackView extends FrameLayout {
}
}
/*
* Sets the action to run to dismiss the currently dragging object (either the stack or an
* individual bubble).
*/
public void setReleasedInDismissTargetAction(Runnable action) {
mReleasedInDismissTargetAction = action;
}
/**
* Dismiss the stack of bubbles.
*
@@ -1200,54 +1393,6 @@ public class BubbleStackView extends FrameLayout {
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED);
}
/**
* @return the view the touch event is on
*/
@Nullable
public View getTargetView(MotionEvent event) {
float x = event.getRawX();
float y = event.getRawY();
if (mIsExpanded) {
if (isIntersecting(mBubbleContainer, x, y)) {
if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
&& isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
return mBubbleOverflow.getBtn();
}
// Could be tapping or dragging a bubble while expanded
for (int i = 0; i < getBubbleCount(); i++) {
BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i);
if (isIntersecting(view, x, y)) {
return view;
}
}
}
BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
if (bev.intersectingTouchableContent((int) x, (int) y)) {
return bev;
}
// Outside of the parts we care about.
return null;
} else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
return mFlyout;
} else if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
View bubbleChild = mBubbleContainer.getChildAt(0);
if (isIntersecting(bubbleChild, x, y)) {
return this;
} else if (isIntersecting(mUserEducationView, x, y)) {
return mUserEducationView;
} else {
return null;
}
}
// If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
return this;
}
View getFlyoutView() {
return mFlyout;
}
/**
* @deprecated use {@link #setExpanded(boolean)} and
* {@link BubbleData#setSelectedBubble(Bubble)}
@@ -1385,124 +1530,70 @@ public class BubbleStackView extends FrameLayout {
}
}
/** Called when the collapsed stack is tapped on. */
void onStackTapped() {
if (!maybeShowStackUserEducation()) {
mBubbleData.setExpanded(true);
}
/**
* This method is called by {@link android.app.ActivityView} because the BubbleStackView has a
* higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV).
* ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided
* touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to
* the special nature of ActivityView, it does not respect the standard
* {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for
* this purpose.
*
* BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation
* properties for performance reasons. This means that the default implementation of this method
* subtracts the entirety of the screen from the ActivityView's touchable region, resulting in
* it not receiving any touch events. This was previously addressed by returning false in the
* stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any
* touch handlers in the stack or its child views.
*
* To support touch handlers, we're overriding this method to leave the ActivityView's touchable
* region alone. The only touchable part of the stack that can ever overlap the AV is a
* dragged-out bubble that is animating back into the row of bubbles. It's not worth continually
* updating the touchable region to allow users to grab a bubble while it completes its ~50ms
* animation back to the bubble row.
*
* NOTE: Any future additions to the stack that obscure the ActivityView region will need their
* bounds subtracted here in order to receive touch events.
*/
@Override
public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
}
/** Called when a drag operation on an individual bubble has started. */
public void onBubbleDragStart(View bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragStart: bubble=" + ((BadgedImageView) bubble).getKey());
}
if (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView())) {
return;
}
mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget);
// We're dragging an individual bubble, so set the magnetized object to the magnetized
// bubble.
mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener);
maybeShowManageEducation(false);
/**
* If you're here because you're not receiving touch events on a view that is a descendant of
* BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the
* bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView
* consumes all touch events within its bounds, even for views like the BubbleStackView that are
* above it. It ignores typical view touch handling methods like this one and
* dispatchTouchEvent.
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
/** Called with the coordinates to which an individual bubble has been dragged. */
public void onBubbleDragged(View bubble, float x, float y) {
if (!mIsExpanded || mIsExpansionAnimating
|| (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
return;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean dispatched = super.dispatchTouchEvent(ev);
// If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
// at the front of the stack (under the touch position). Subsequent ACTION_MOVE events will
// then be passed to the new bubble, which will not consume them since it hasn't received an
// ACTION_DOWN yet. Work around this by passing MotionEvents directly to the touch handler
// until the current gesture ends with an ACTION_UP event.
if (!dispatched && !mIsExpanded && mIsGestureInProgress) {
dispatched = mBubbleTouchListener.onTouch(this /* view */, ev);
}
mExpandedAnimationController.dragBubbleOut(bubble, x, y);
springInDismissTarget();
mIsGestureInProgress =
ev.getAction() != MotionEvent.ACTION_UP
&& ev.getAction() != MotionEvent.ACTION_CANCEL;
return dispatched;
}
/** Called when a drag operation on an individual bubble has finished. */
public void onBubbleDragFinish(
View bubble, float x, float y, float velX, float velY) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
}
if (!mIsExpanded || mIsExpansionAnimating
|| (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
return;
}
mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
hideDismissTarget();
}
/** Expands the clicked bubble. */
public void expandBubble(Bubble bubble) {
if (bubble != null && bubble.equals(mBubbleData.getSelectedBubble())) {
// If the bubble we're supposed to expand is the selected bubble, that means the
// overflow bubble is currently expanded. Don't tell BubbleData to set this bubble as
// selected, since it already is. Just call the stack's setSelectedBubble to expand it.
setSelectedBubble(bubble);
} else {
mBubbleData.setSelectedBubble(bubble);
}
}
void onDragStart() {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onDragStart()");
}
if (mIsExpanded || mIsExpansionAnimating) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "mIsExpanded or mIsExpansionAnimating");
}
return;
}
mStackAnimationController.cancelStackPositionAnimations();
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
// Since we're dragging the stack, set the magnetized object to the magnetized stack.
mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
mMagnetizedObject.setMagnetListener(mStackMagnetListener);
}
void onDragged(float x, float y) {
if (mIsExpanded || mIsExpansionAnimating) {
return;
}
hideStackUserEducation(false /* fromExpansion */);
springInDismissTarget();
mStackAnimationController.moveStackFromTouch(x, y);
}
void onDragFinish(float x, float y, float velX, float velY) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onDragFinish");
}
if (mIsExpanded || mIsExpansionAnimating) {
return;
}
final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
logBubbleEvent(null /* no bubble associated with bubble stack move */,
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
mStackOnLeftOrWillBe = newStackX <= 0;
updateBubbleZOrdersAndDotPosition(true /* animate */);
hideDismissTarget();
}
void onFlyoutDragStart() {
mFlyout.removeCallbacks(mHideFlyout);
}
void onFlyoutDragged(float deltaX) {
void setFlyoutStateForDragLength(float deltaX) {
// This shouldn't happen, but if it does, just wait until the flyout lays out. This method
// is continually called.
if (mFlyout.getWidth() <= 0) {
@@ -1538,61 +1629,29 @@ public class BubbleStackView extends FrameLayout {
mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
}
void onFlyoutTapped() {
if (maybeShowStackUserEducation()) {
// If we're showing user education, don't open the bubble show the education first
mBubbleToExpandAfterFlyoutCollapse = null;
} else {
mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
}
mFlyout.removeCallbacks(mHideFlyout);
mHideFlyout.run();
}
/**
* Called when the flyout drag has finished, and returns true if the gesture successfully
* dismissed the flyout.
*/
void onFlyoutDragFinished(float deltaX, float velX) {
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
final boolean metRequiredVelocity =
onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
final boolean metRequiredDeltaX =
onLeft
? deltaX < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
: deltaX > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
final boolean shouldDismiss = metRequiredVelocity || (metRequiredDeltaX && !isCancelFling);
mFlyout.removeCallbacks(mHideFlyout);
animateFlyoutCollapsed(shouldDismiss, velX);
maybeShowStackUserEducation();
}
/**
* Called when the first touch event of a gesture (stack drag, bubble drag, flyout drag, etc.)
* is received.
*/
void onGestureStart() {
mIsGestureInProgress = true;
}
/** Called when a gesture is completed or cancelled. */
void onGestureFinished() {
mIsGestureInProgress = false;
if (mIsExpanded) {
mExpandedAnimationController.onGestureFinished();
}
}
/** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
boolean passEventToMagnetizedObject(MotionEvent event) {
private boolean passEventToMagnetizedObject(MotionEvent event) {
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
}
/**
* Dismisses the magnetized object - either an individual bubble, if we're expanded, or the
* stack, if we're collapsed.
*/
private void dismissMagnetizedObject() {
if (mIsExpanded) {
final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
mBubbleData.notificationEntryRemoved(
draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
}
} else {
mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
}
}
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
mDesaturateAndDarkenTargetView = targetView;
@@ -1624,7 +1683,7 @@ public class BubbleStackView extends FrameLayout {
}
/** Animates in the dismiss target. */
private void springInDismissTarget() {
private void springInDismissTargetMaybe() {
if (mShowingDismiss) {
return;
}
@@ -1827,13 +1886,6 @@ public class BubbleStackView extends FrameLayout {
return 0;
}
private boolean isIntersecting(View view, float x, float y) {
mTempLoc = view.getLocationOnScreen();
mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
mTempLoc[1] + view.getHeight());
return mTempRect.contains(x, y);
}
private void requestUpdate() {
if (mViewUpdatedRequested || mIsExpansionAnimating) {
return;

View File

@@ -1,221 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.bubbles;
import android.content.Context;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.systemui.Dependency;
/**
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
* dismissing, and flings.
*/
class BubbleTouchHandler implements View.OnTouchListener {
private final PointF mTouchDown = new PointF();
private final PointF mViewPositionOnTouchDown = new PointF();
private final BubbleStackView mStack;
private final BubbleData mBubbleData;
private BubbleController mController = Dependency.get(BubbleController.class);
private boolean mMovedEnough;
private int mTouchSlopSquared;
private VelocityTracker mVelocityTracker;
/** View that was initially touched, when we received the first ACTION_DOWN event. */
private View mTouchedView;
BubbleTouchHandler(BubbleStackView stackView,
BubbleData bubbleData, Context context) {
final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mTouchSlopSquared = touchSlop * touchSlop;
mBubbleData = bubbleData;
mStack = stackView;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getActionMasked();
// If we aren't currently in the process of touching a view, figure out what we're touching.
// It'll be the stack, an individual bubble, or nothing.
if (mTouchedView == null) {
mTouchedView = mStack.getTargetView(event);
}
// If this is an ACTION_OUTSIDE event, or the stack reported that we aren't touching
// anything, collapse the stack.
if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
mBubbleData.setExpanded(false);
mStack.hideStackUserEducation(false /* fromExpansion */);
resetForNextGesture();
return false;
}
if (!(mTouchedView instanceof BadgedImageView)
&& !(mTouchedView instanceof BubbleStackView)
&& !(mTouchedView instanceof BubbleFlyoutView)) {
// Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
// of expanded view).
mStack.maybeShowManageEducation(false);
resetForNextGesture();
return false;
}
final boolean isStack = mStack.equals(mTouchedView);
final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
final float rawX = event.getRawX();
final float rawY = event.getRawY();
// The coordinates of the touch event, in terms of the touched view's position.
final float viewX = mViewPositionOnTouchDown.x + rawX - mTouchDown.x;
final float viewY = mViewPositionOnTouchDown.y + rawY - mTouchDown.y;
switch (action) {
case MotionEvent.ACTION_DOWN:
trackMovement(event);
mTouchDown.set(rawX, rawY);
mStack.onGestureStart();
if (isStack) {
mViewPositionOnTouchDown.set(mStack.getStackPosition());
// Dismiss the entire stack if it's released in the dismiss target.
mStack.setReleasedInDismissTargetAction(
() -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE));
mStack.onDragStart();
mStack.passEventToMagnetizedObject(event);
} else if (isFlyout) {
mStack.onFlyoutDragStart();
} else {
mViewPositionOnTouchDown.set(
mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
// Dismiss only the dragged-out bubble if it's released in the target.
final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey();
mStack.setReleasedInDismissTargetAction(() -> {
final Bubble bubble =
mBubbleData.getBubbleWithKey(individualBubbleKey);
// bubble can be null if the user is in the middle of
// dismissing the bubble, but the app also sent a cancel
if (bubble != null) {
mController.removeBubble(bubble.getEntry(),
BubbleController.DISMISS_USER_GESTURE);
}
});
mStack.onBubbleDragStart(mTouchedView);
mStack.passEventToMagnetizedObject(event);
}
break;
case MotionEvent.ACTION_MOVE:
trackMovement(event);
final float deltaX = rawX - mTouchDown.x;
final float deltaY = rawY - mTouchDown.y;
if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
mMovedEnough = true;
}
if (mMovedEnough) {
if (isFlyout) {
mStack.onFlyoutDragged(deltaX);
} else if (!mStack.passEventToMagnetizedObject(event)) {
// If the magnetic target doesn't consume the event, drag the stack or
// bubble.
if (isStack) {
mStack.onDragged(viewX, viewY);
} else {
mStack.onBubbleDragged(mTouchedView, viewX, viewY);
}
}
}
break;
case MotionEvent.ACTION_CANCEL:
resetForNextGesture();
break;
case MotionEvent.ACTION_UP:
trackMovement(event);
mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
final float velX = mVelocityTracker.getXVelocity();
final float velY = mVelocityTracker.getYVelocity();
if (isFlyout && mMovedEnough) {
mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX);
} else if (isFlyout) {
if (!mBubbleData.isExpanded() && !mMovedEnough) {
mStack.onFlyoutTapped();
}
} else if (mMovedEnough) {
if (!mStack.passEventToMagnetizedObject(event)) {
// If the magnetic target didn't consume the event, tell the stack to finish
// the drag.
if (isStack) {
mStack.onDragFinish(viewX, viewY, velX, velY);
} else {
mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
}
}
} else if (mTouchedView == mStack.getExpandedBubbleView()) {
mBubbleData.setExpanded(false);
} else if (isStack) {
mStack.onStackTapped();
} else {
final String key = ((BadgedImageView) mTouchedView).getKey();
if (key == BubbleOverflow.KEY) {
mStack.showOverflow();
} else {
mStack.expandBubble(mBubbleData.getBubbleWithKey(key));
}
}
resetForNextGesture();
break;
}
return true;
}
/** Clears all touch-related state. */
private void resetForNextGesture() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mTouchedView = null;
mMovedEnough = false;
mStack.onGestureFinished();
}
private void trackMovement(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
}

View File

@@ -252,8 +252,14 @@ public class ExpandedAnimationController
mSpringToTouchOnNextMotionEvent = true;
}
/** Prepares the given bubble to be dragged out. */
public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) {
/**
* Prepares the given bubble view to be dragged out, using the provided magnetic target and
* listener.
*/
public void prepareForBubbleDrag(
View bubble,
MagnetizedObject.MagneticTarget target,
MagnetizedObject.MagnetListener listener) {
mLayout.cancelAnimationsOnView(bubble);
bubble.setTranslationZ(Short.MAX_VALUE);
@@ -277,6 +283,7 @@ public class ExpandedAnimationController
}
};
mMagnetizedBubbleDraggingOut.addTarget(target);
mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
}

View File

@@ -1117,9 +1117,4 @@ public class PhysicsAnimationLayout extends FrameLayout {
mAssociatedController = controller;
}
}
@Override
protected boolean canReceivePointerEvents() {
return false;
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.util
import android.graphics.PointF
import android.os.Handler
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import kotlin.math.hypot
/**
* Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
* the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
* view's initial position.
*/
abstract class RelativeTouchListener : View.OnTouchListener {
/**
* Called when an ACTION_DOWN event is received for the given view.
*
* @return False if the object is not interested in MotionEvents at this time, or true if we
* should consume this event and subsequent events, and begin calling [onMove].
*/
abstract fun onDown(v: View, ev: MotionEvent): Boolean
/**
* Called when an ACTION_MOVE event is received for the given view. This signals that the view
* is being dragged.
*
* @param viewInitialX The view's translationX value when this touch gesture started.
* @param viewInitialY The view's translationY value when this touch gesture started.
* @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
* @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
*/
abstract fun onMove(
v: View,
ev: MotionEvent,
viewInitialX: Float,
viewInitialY: Float,
dx: Float,
dy: Float
)
/**
* Called when an ACTION_UP event is received for the given view. This signals that a drag or
* fling gesture has completed.
*
* @param viewInitialX The view's translationX value when this touch gesture started.
* @param viewInitialY The view's translationY value when this touch gesture started.
* @param dx Horizontal distance covered, in pixels.
* @param dy Vertical distance covered, in pixels.
* @param velX The final horizontal velocity of the gesture, in pixels/second.
* @param velY The final vertical velocity of the gesture, in pixels/second.
*/
abstract fun onUp(
v: View,
ev: MotionEvent,
viewInitialX: Float,
viewInitialY: Float,
dx: Float,
dy: Float,
velX: Float,
velY: Float
)
/** The raw coordinates of the last ACTION_DOWN event. */
private val touchDown = PointF()
/** The coordinates of the view, at the time of the last ACTION_DOWN event. */
private val viewPositionOnTouchDown = PointF()
private val velocityTracker = VelocityTracker.obtain()
private var touchSlop: Int = -1
private var movedEnough = false
private val handler = Handler()
private var performedLongClick = false
@Suppress("UNCHECKED_CAST")
override fun onTouch(v: View, ev: MotionEvent): Boolean {
addMovement(ev)
val dx = ev.rawX - touchDown.x
val dy = ev.rawY - touchDown.y
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
if (!onDown(v, ev)) {
return false
}
// Grab the touch slop, it might have changed if the config changed since the
// last gesture.
touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
touchDown.set(ev.rawX, ev.rawY)
viewPositionOnTouchDown.set(v.translationX, v.translationY)
performedLongClick = false
handler.postDelayed({
performedLongClick = v.performLongClick()
}, ViewConfiguration.getLongPressTimeout().toLong())
}
MotionEvent.ACTION_MOVE -> {
if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
movedEnough = true
handler.removeCallbacksAndMessages(null)
}
if (movedEnough) {
onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
}
}
MotionEvent.ACTION_UP -> {
if (movedEnough) {
velocityTracker.computeCurrentVelocity(1000 /* units */)
onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
velocityTracker.xVelocity, velocityTracker.yVelocity)
} else if (!performedLongClick) {
v.performClick()
} else {
handler.removeCallbacksAndMessages(null)
}
velocityTracker.clear()
movedEnough = false
}
}
return true
}
/**
* Adds a movement to the velocity tracker using raw screen coordinates.
*/
private fun addMovement(event: MotionEvent) {
val deltaX = event.rawX - event.x
val deltaY = event.rawY - event.y
event.offsetLocation(deltaX, deltaY)
velocityTracker.addMovement(event)
event.offsetLocation(-deltaX, -deltaY)
}
}

View File

@@ -374,7 +374,7 @@ public class BubbleControllerTest extends SysuiTestCase {
assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
assertTrue(mBubbleController.hasBubbles());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
assertFalse(mNotificationShadeWindowController.getBubblesShowing());
verify(mNotificationEntryManager, times(3)).updateNotifications(any());
assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -399,7 +399,7 @@ public class BubbleControllerTest extends SysuiTestCase {
// Expand the stack
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -436,7 +436,7 @@ public class BubbleControllerTest extends SysuiTestCase {
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -448,7 +448,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mRow2.getEntry()));
// Switch which bubble is expanded
mBubbleController.selectBubble(mRow.getEntry().getKey());
mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
mBubbleData.setExpanded(true);
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -482,7 +482,7 @@ public class BubbleControllerTest extends SysuiTestCase {
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -510,7 +510,7 @@ public class BubbleControllerTest extends SysuiTestCase {
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -544,7 +544,7 @@ public class BubbleControllerTest extends SysuiTestCase {
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mSysUiStateBubblesExpanded);
@@ -726,7 +726,7 @@ public class BubbleControllerTest extends SysuiTestCase {
public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(2)).send();
}

View File

@@ -321,7 +321,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
assertTrue(mBubbleController.hasBubbles());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
assertFalse(mNotificationShadeWindowController.getBubblesShowing());
verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -344,7 +344,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
// Expand the stack
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -376,7 +376,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -385,7 +385,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
// Switch which bubble is expanded
mBubbleController.selectBubble(mRow.getEntry().getKey());
mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
mBubbleData.setExpanded(true);
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -416,7 +416,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -442,7 +442,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
// Expand
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
@@ -474,7 +474,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleController.expandStack();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -628,7 +628,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(2)).send();
}