Delete BubbleTouchHandler in favor of more traditional touch handling.
This will scale better as we add more views and gestures. Test: manual (fixing test suite now, just getting out for review!) Bug: 152877243 Merged-In: I46001c24f4de58c3d70b7629a22b930fa791b77a Change-Id: I46001c24f4de58c3d70b7629a22b930fa791b77a
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1117,9 +1117,4 @@ public class PhysicsAnimationLayout extends FrameLayout {
|
||||
mAssociatedController = controller;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canReceivePointerEvents() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user