diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index 3976d63f52202..a9ab2cf7f3c83 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.util.MathUtils.constrain; + import static java.util.Objects.requireNonNull; import android.animation.ValueAnimator; @@ -57,6 +59,7 @@ class MenuAnimationController { private final MenuView mMenuView; private final ValueAnimator mFadeOutAnimator; private final Handler mHandler; + private boolean mIsMovedToEdge; private boolean mIsFadeEffectEnabled; // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link @@ -96,6 +99,12 @@ class MenuAnimationController { } } + void moveAndPersistPosition(PointF position) { + moveToPosition(position); + mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); + constrainPositionAndUpdate(position); + } + void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) { final boolean shouldMenuFlingLeft = isOnLeftSide() ? velocityX < ESCAPE_VELOCITY @@ -190,10 +199,63 @@ class MenuAnimationController { springAnimation.animateToFinalPosition(finalPosition); } + /** + * Determines whether to hide the menu to the edge of the screen with the given current + * translation x of the menu view. It should be used when receiving the action up touch event. + * + * @param currentXTranslation the current translation x of the menu view. + * @return true if the menu would be hidden to the edge, otherwise false. + */ + boolean maybeMoveToEdgeAndHide(float currentXTranslation) { + final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); + + // If the translation x is zero, it should be at the left of the bound. + if (currentXTranslation < draggableBounds.left + || currentXTranslation > draggableBounds.right) { + moveToEdgeAndHide(); + return true; + } + + fadeOutIfEnabled(); + return false; + } + private boolean isOnLeftSide() { return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX(); } + boolean isMovedToEdge() { + return mIsMovedToEdge; + } + + void moveToEdgeAndHide() { + mIsMovedToEdge = true; + + final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); + final float endY = constrain(mMenuView.getTranslationY(), draggableBounds.top, + draggableBounds.bottom); + final float menuHalfWidth = mMenuView.getWidth() / 2.0f; + final float endX = isOnLeftSide() + ? draggableBounds.left - menuHalfWidth + : draggableBounds.right + menuHalfWidth; + moveAndPersistPosition(new PointF(endX, endY)); + + // Keep the touch region let users could click extra space to pop up the menu view + // from the screen edge + mMenuView.onBoundsInParentChanged(isOnLeftSide() + ? draggableBounds.left + : draggableBounds.right, (int) mMenuView.getTranslationY()); + + fadeOutIfEnabled(); + } + + void moveOutEdgeAndShow() { + mIsMovedToEdge = false; + + mMenuView.onPositionChanged(); + mMenuView.onEdgeChangedIfNeeded(); + } + void cancelAnimations() { cancelAnimation(DynamicAnimation.TRANSLATION_X); cancelAnimation(DynamicAnimation.TRANSLATION_Y); @@ -213,7 +275,12 @@ class MenuAnimationController { private void onSpringAnimationEnd(PointF position) { mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); + constrainPositionAndUpdate(position); + fadeOutIfEnabled(); + } + + private void constrainPositionAndUpdate(PointF position) { final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); // Have the space gap margin between the top bound and the menu view, so actually the // position y range needs to cut the margin. diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java index 1c312e235fd0a..3146c9f0d2af4 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java @@ -79,9 +79,11 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener { final float endX = mMenuTranslationDown.x + dx; mIsDragging = false; - mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS); - mMenuAnimationController.flingMenuThenSpringToEdge(endX, - mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + if (!mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) { + mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS); + mMenuAnimationController.flingMenuThenSpringToEdge(endX, + mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + } // Avoid triggering the listener of the item. return true; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index e2c2bc40a9a8f..7f43e62519cbe 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -148,7 +148,7 @@ class MenuView extends FrameLayout implements onPositionChanged(); } - private void onPositionChanged() { + void onPositionChanged() { final PointF position = mMenuViewAppearance.getMenuPosition(); mMenuAnimationController.moveToPosition(position); onBoundsInParentChanged((int) position.x, (int) position.y); @@ -204,6 +204,33 @@ class MenuView extends FrameLayout implements onEdgeChangedIfNeeded(); } + /** + * Uses the touch events from the parent view to identify if users clicked the extra + * space of the menu view. If yes, will use the percentage position and update the + * translations of the menu view to meet the effect of moving out from the edge. It’s only + * used when the menu view is hidden to the screen edge. + * + * @param x the current x of the touch event from the parent {@link MenuViewLayer} of the + * {@link MenuView}. + * @param y the current y of the touch event from the parent {@link MenuViewLayer} of the + * {@link MenuView}. + * @return true if consume the touch event, otherwise false. + */ + boolean maybeMoveOutEdgeAndShow(int x, int y) { + // Utilizes the touch region of the parent view to implement that users could tap extra + // the space region to show the menu from the edge. + if (!mMenuAnimationController.isMovedToEdge() || !mBoundsInParent.contains(x, y)) { + return false; + } + + mMenuAnimationController.fadeInNowIfEnabled(); + + mMenuAnimationController.moveOutEdgeAndShow(); + + mMenuAnimationController.fadeOutIfEnabled(); + return true; + } + void show() { mMenuViewModel.getPercentagePositionData().observeForever(mPercentagePositionObserver); mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 262f43b5bccb2..5252519e9faf4 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -19,6 +19,7 @@ package com.android.systemui.accessibility.floatingmenu; import android.annotation.IntDef; import android.annotation.SuppressLint; import android.content.Context; +import android.view.MotionEvent; import android.view.WindowManager; import android.widget.FrameLayout; @@ -53,6 +54,15 @@ class MenuViewLayer extends FrameLayout { addView(mMenuView, LayerIndex.MENU_VIEW); } + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (mMenuView.maybeMoveOutEdgeAndShow((int) event.getX(), (int) event.getY())) { + return true; + } + + return super.onInterceptTouchEvent(event); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index 081b182781409..c5b9a294fc341 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -130,6 +130,28 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { anyFloat()); } + @Test + public void dragMenuOutOfBoundsAndDrop_moveToLeftEdge_shouldMoveToEdgeAndHide() { + final int offset = -100; + final MotionEvent stubDownEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1, + MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(), + mStubMenuView.getTranslationY()); + final MotionEvent stubMoveEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3, + MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset, + mStubMenuView.getTranslationY() + offset); + final MotionEvent stubUpEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 5, + MotionEvent.ACTION_UP, mStubMenuView.getTranslationX() + offset, + mStubMenuView.getTranslationY() + offset); + mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent); + mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent); + mTouchHandler.onInterceptTouchEvent(mStubListView, stubUpEvent); + + verify(mMenuAnimationController).moveToEdgeAndHide(); + } + @After public void tearDown() { mMotionEventHelper.recycleEvents();