diff options
7 files changed, 563 insertions, 7 deletions
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 9d2a4d74a0e6..5d4c77663dab 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -17,23 +17,216 @@ package com.android.systemui.accessibility.floatingmenu; import android.graphics.PointF; +import android.graphics.Rect; +import android.util.Log; +import android.view.View; import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FlingAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.HashMap; /** - * Controls the interaction animations of the menu view {@link MenuView}. + * Controls the interaction animations of the {@link MenuView}. Also, it will use the relative + * coordinate based on the {@link MenuViewLayer} to compute the offset of the {@link MenuView}. */ class MenuAnimationController { + private static final String TAG = "MenuAnimationController"; + private static final boolean DEBUG = false; + private static final float MIN_PERCENT = 0.0f; + private static final float MAX_PERCENT = 1.0f; + private static final float FLING_FRICTION_SCALAR = 1.9f; + private static final float DEFAULT_FRICTION = 4.2f; + private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f; + private static final float SPRING_STIFFNESS = 700f; + private static final float ESCAPE_VELOCITY = 750f; + private final MenuView mMenuView; + // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link + // DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler + private final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mPositionAnimations = + new HashMap<>(); + MenuAnimationController(MenuView menuView) { mMenuView = menuView; } void moveToPosition(PointF position) { - DynamicAnimation.TRANSLATION_X.setValue(mMenuView, position.x); - DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, position.y); + moveToPositionX(position.x); + moveToPositionY(position.y); + } + + void moveToPositionX(float positionX) { + DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX); + } + + private void moveToPositionY(float positionY) { + DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY); + } + + void moveToPositionYIfNeeded(float positionY) { + // If the list view was out of screen bounds, it would allow users to nest scroll inside + // and avoid conflicting with outer scroll. + final RecyclerView listView = (RecyclerView) mMenuView.getChildAt(/* index= */ 0); + if (listView.getOverScrollMode() == View.OVER_SCROLL_NEVER) { + moveToPositionY(positionY); + } + } + + void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) { + final boolean shouldMenuFlingLeft = isOnLeftSide() + ? velocityX < ESCAPE_VELOCITY + : velocityX < -ESCAPE_VELOCITY; + + final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); + final float finalPositionX = shouldMenuFlingLeft + ? draggableBounds.left : draggableBounds.right; + final float minimumVelocityToReachEdge = + (finalPositionX - x) * (FLING_FRICTION_SCALAR * DEFAULT_FRICTION); + + final float startXVelocity = shouldMenuFlingLeft + ? Math.min(minimumVelocityToReachEdge, velocityX) + : Math.max(minimumVelocityToReachEdge, velocityX); + + flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X, + startXVelocity, + FLING_FRICTION_SCALAR, + new SpringForce() + .setStiffness(SPRING_STIFFNESS) + .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + finalPositionX); + + flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y, + velocityY, + FLING_FRICTION_SCALAR, + new SpringForce() + .setStiffness(SPRING_STIFFNESS) + .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + /* finalPosition= */ null); + } + + private void flingThenSpringMenuWith(DynamicAnimation.ViewProperty property, float velocity, + float friction, SpringForce spring, Float finalPosition) { + + final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property); + final float currentValue = menuPositionProperty.getValue(mMenuView); + final Rect bounds = mMenuView.getMenuDraggableBounds(); + final float min = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.left + : bounds.top; + final float max = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.right + : bounds.bottom; + + final FlingAnimation flingAnimation = new FlingAnimation(mMenuView, menuPositionProperty); + flingAnimation.setFriction(friction) + .setStartVelocity(velocity) + .setMinValue(Math.min(currentValue, min)) + .setMaxValue(Math.max(currentValue, max)) + .addEndListener((animation, canceled, endValue, endVelocity) -> { + if (canceled) { + if (DEBUG) { + Log.d(TAG, "The fling animation was canceled."); + } + + return; + } + + final float endPosition = finalPosition != null + ? finalPosition + : Math.max(min, Math.min(max, endValue)); + springMenuWith(property, spring, endVelocity, endPosition); + }); + + cancelAnimation(property); + mPositionAnimations.put(property, flingAnimation); + flingAnimation.start(); + } + + private void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring, + float velocity, float finalPosition) { + final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property); + final SpringAnimation springAnimation = + new SpringAnimation(mMenuView, menuPositionProperty) + .setSpring(spring) + .addEndListener((animation, canceled, endValue, endVelocity) -> { + if (canceled || endValue != finalPosition) { + return; + } + + onSpringAnimationEnd(new PointF(mMenuView.getTranslationX(), + mMenuView.getTranslationY())); + }) + .setStartVelocity(velocity); + + cancelAnimation(property); + mPositionAnimations.put(property, springAnimation); + springAnimation.animateToFinalPosition(finalPosition); + } + + private boolean isOnLeftSide() { + return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX(); + } + + void cancelAnimations() { + cancelAnimation(DynamicAnimation.TRANSLATION_X); + cancelAnimation(DynamicAnimation.TRANSLATION_Y); + } + + private void cancelAnimation(DynamicAnimation.ViewProperty property) { + if (!mPositionAnimations.containsKey(property)) { + return; + } + + mPositionAnimations.get(property).cancel(); + } + + void onDraggingStart() { + mMenuView.onDraggingStart(); + } + + private void onSpringAnimationEnd(PointF position) { mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); + + 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. + position.offset(-draggableBounds.left, -draggableBounds.top); + + final float percentageX = position.x < draggableBounds.centerX() + ? MIN_PERCENT : MAX_PERCENT; + + final float percentageY = position.y < 0 || draggableBounds.height() == 0 + ? MIN_PERCENT + : Math.min(MAX_PERCENT, position.y / draggableBounds.height()); + mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); + } + + static class MenuPositionProperty + extends FloatPropertyCompat<MenuView> { + private final DynamicAnimation.ViewProperty mProperty; + + MenuPositionProperty(DynamicAnimation.ViewProperty property) { + super(property.toString()); + mProperty = property; + } + + @Override + public float getValue(MenuView menuView) { + return mProperty.getValue(menuView); + } + + @Override + public void setValue(MenuView menuView, float value) { + mProperty.setValue(menuView, value); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java new file mode 100644 index 000000000000..c9db964972b6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 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.accessibility.floatingmenu; + +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +/** + * Controls the all touch events of the accessibility target features view{@link RecyclerView} in + * the {@link MenuView}. And then compute the gestures' velocity for fling and spring + * animations. + */ +class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener { + private static final int VELOCITY_UNIT_SECONDS = 1000; + private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + private final MenuAnimationController mMenuAnimationController; + private final PointF mDown = new PointF(); + private final PointF mMenuTranslationDown = new PointF(); + private boolean mIsDragging = false; + private float mTouchSlop; + + MenuListViewTouchHandler(MenuAnimationController menuAnimationController) { + mMenuAnimationController = menuAnimationController; + } + + @Override + public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, + @NonNull MotionEvent motionEvent) { + + final View menuView = (View) recyclerView.getParent(); + addMovement(motionEvent); + + final float dx = motionEvent.getRawX() - mDown.x; + final float dy = motionEvent.getRawY() - mDown.y; + + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + mTouchSlop = ViewConfiguration.get(recyclerView.getContext()).getScaledTouchSlop(); + mDown.set(motionEvent.getRawX(), motionEvent.getRawY()); + mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY()); + + mMenuAnimationController.cancelAnimations(); + break; + case MotionEvent.ACTION_MOVE: + if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) { + if (!mIsDragging) { + mIsDragging = true; + mMenuAnimationController.onDraggingStart(); + } + + mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx); + mMenuAnimationController.moveToPositionYIfNeeded(mMenuTranslationDown.y + dy); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mIsDragging) { + final float endX = mMenuTranslationDown.x + dx; + mIsDragging = false; + + mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS); + mMenuAnimationController.flingMenuThenSpringToEdge(endX, + mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + + // Avoid triggering the listener of the item. + return true; + } + + break; + default: // Do nothing + } + + // not consume all the events here because keeping the scroll behavior of list view. + return false; + } + + @Override + public void onTouchEvent(@NonNull RecyclerView recyclerView, + @NonNull MotionEvent motionEvent) { + // Do nothing + } + + @Override + public void onRequestDisallowInterceptTouchEvent(boolean b) { + // Do nothing + } + + /** + * Adds a movement to the velocity tracker using raw screen coordinates. + */ + private void addMovement(MotionEvent motionEvent) { + final float deltaX = motionEvent.getRawX() - motionEvent.getX(); + final float deltaY = motionEvent.getRawY() - motionEvent.getY(); + motionEvent.offsetLocation(deltaX, deltaY); + mVelocityTracker.addMovement(motionEvent); + motionEvent.offsetLocation(-deltaX, -deltaY); + } +} 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 d6f8885f6230..e90e54cc71e5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -39,7 +39,7 @@ import java.util.Collections; import java.util.List; /** - * The container view displays the accessibility features. + * The menu view displays the accessibility features. */ @SuppressLint("ViewConstructor") class MenuView extends FrameLayout implements @@ -65,10 +65,15 @@ class MenuView extends FrameLayout implements mMenuViewModel = menuViewModel; mMenuViewAppearance = menuViewAppearance; mMenuAnimationController = new MenuAnimationController(this); + mAdapter = new AccessibilityTargetAdapter(mTargetFeatures); mTargetFeaturesView = new RecyclerView(context); mTargetFeaturesView.setAdapter(mAdapter); mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context)); + final MenuListViewTouchHandler menuListViewTouchHandler = + new MenuListViewTouchHandler(mMenuAnimationController); + addOnItemTouchListenerToList(menuListViewTouchHandler); + setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); // Avoid drawing out of bounds of the parent view setClipToOutline(true); @@ -93,6 +98,10 @@ class MenuView extends FrameLayout implements mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode()); } + void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) { + mTargetFeaturesView.addOnItemTouchListener(listener); + } + @SuppressLint("NotifyDataSetChanged") private void onItemSizeChanged() { mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding()); @@ -110,6 +119,16 @@ class MenuView extends FrameLayout implements setLayoutParams(layoutParams); } + void onEdgeChangedIfNeeded() { + final Rect draggableBounds = mMenuViewAppearance.getMenuDraggableBounds(); + if (getTranslationX() != draggableBounds.left + && getTranslationX() != draggableBounds.right) { + return; + } + + onEdgeChanged(); + } + private void onEdgeChanged() { final int[] insets = mMenuViewAppearance.getMenuInsets(); getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2], @@ -159,6 +178,17 @@ class MenuView extends FrameLayout implements onPositionChanged(); } + Rect getMenuDraggableBounds() { + return mMenuViewAppearance.getMenuDraggableBounds(); + } + + void persistPositionAndUpdateEdge(Position percentagePosition) { + mMenuViewModel.updateMenuSavingPosition(percentagePosition); + mMenuViewAppearance.setPercentagePosition(percentagePosition); + + onEdgeChangedIfNeeded(); + } + void show() { mMenuViewModel.getPercentagePositionData().observeForever(mPercentagePositionObserver); mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver); @@ -180,6 +210,15 @@ class MenuView extends FrameLayout implements getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater); } + void onDraggingStart() { + final int[] insets = mMenuViewAppearance.getMenuMovingStateInsets(); + getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2], + insets[3]); + + final GradientDrawable gradientDrawable = getContainerViewGradient(); + gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuMovingStateRadii()); + } + void onBoundsInParentChanged(int newLeft, int newTop) { mBoundsInParent.offsetTo(newLeft, newTop); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index bbf967310ca3..2ff0703458bd 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -193,6 +193,15 @@ class MenuViewAppearance { return new int[]{left, 0, right, 0}; } + int[] getMenuMovingStateInsets() { + return new int[]{0, 0, 0, 0}; + } + + float[] getMenuMovingStateRadii() { + final float radius = getMenuRadius(mTargetFeaturesSize); + return new float[]{radius, radius, radius, radius, radius, radius, radius, radius}; + } + int getMenuStrokeWidth() { return mStrokeWidth; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java index f8fd4f599796..24bed3ef9c47 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java @@ -50,6 +50,10 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { mSizeTypeData.setValue(newSizeType); } + void updateMenuSavingPosition(Position percentagePosition) { + mInfoRepository.updateMenuSavingPosition(percentagePosition); + } + LiveData<Position> getPercentagePositionData() { mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue); return mPercentagePositionData; 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 new file mode 100644 index 000000000000..081b18278140 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 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.accessibility.floatingmenu; + +import static android.view.View.OVER_SCROLL_NEVER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.MotionEvent; +import android.view.WindowManager; + +import androidx.recyclerview.widget.RecyclerView; +import androidx.test.filters.SmallTest; + +import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.MotionEventHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Tests for {@link MenuListViewTouchHandler}. */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class MenuListViewTouchHandlerTest extends SysuiTestCase { + private final List<AccessibilityTarget> mStubTargets = new ArrayList<>( + Collections.singletonList(mock(AccessibilityTarget.class))); + private final MotionEventHelper mMotionEventHelper = new MotionEventHelper(); + private MenuView mStubMenuView; + private MenuListViewTouchHandler mTouchHandler; + private MenuAnimationController mMenuAnimationController; + private RecyclerView mStubListView; + + @Before + public void setUp() throws Exception { + final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext); + final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, + windowManager); + mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance); + mStubMenuView.setTranslationX(0); + mStubMenuView.setTranslationY(0); + mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView)); + mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController); + final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets); + mStubListView = (RecyclerView) mStubMenuView.getChildAt(0); + mStubListView.setAdapter(stubAdapter); + } + + @Test + public void onActionDownEvent_shouldCancelAnimations() { + final MotionEvent stubDownEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1, + MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(), + mStubMenuView.getTranslationY()); + + mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent); + + verify(mMenuAnimationController).cancelAnimations(); + } + + @Test + public void onActionMoveEvent_shouldMoveToPosition() { + 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); + mStubListView.setOverScrollMode(OVER_SCROLL_NEVER); + + mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent); + mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent); + + assertThat(mStubMenuView.getTranslationX()).isEqualTo(offset); + assertThat(mStubMenuView.getTranslationY()).isEqualTo(offset); + } + + @Test + public void dragAndDrop_shouldFlingMenuThenSpringToEdge() { + 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).flingMenuThenSpringToEdge(anyFloat(), anyFloat(), + anyFloat()); + } + + @After + public void tearDown() { + mMotionEventHelper.recycleEvents(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 6549de15dad2..742ee53e99b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -24,12 +24,15 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.UiModeManager; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; import org.junit.After; @@ -46,6 +49,8 @@ public class MenuViewTest extends SysuiTestCase { private int mNightMode; private UiModeManager mUiModeManager; private MenuView mMenuView; + private String mLastPosition; + private MenuViewAppearance mStubMenuViewAppearance; @Before public void setUp() throws Exception { @@ -54,9 +59,10 @@ public class MenuViewTest extends SysuiTestCase { mUiModeManager.setNightMode(MODE_NIGHT_YES); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); - final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, - stubWindowManager); - mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance)); + mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); + mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance)); + mLastPosition = Prefs.getString(mContext, + Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null); } @Test @@ -77,8 +83,58 @@ public class MenuViewTest extends SysuiTestCase { assertThat(areInsetsMatched).isTrue(); } + @Test + public void onDraggingStart_matchInsets() { + mMenuView.onDraggingStart(); + final InstantInsetLayerDrawable insetLayerDrawable = + (InstantInsetLayerDrawable) mMenuView.getBackground(); + + assertThat(insetLayerDrawable.getLayerInsetLeft(INDEX_MENU_ITEM)).isEqualTo(0); + assertThat(insetLayerDrawable.getLayerInsetTop(INDEX_MENU_ITEM)).isEqualTo(0); + assertThat(insetLayerDrawable.getLayerInsetRight(INDEX_MENU_ITEM)).isEqualTo(0); + assertThat(insetLayerDrawable.getLayerInsetBottom(INDEX_MENU_ITEM)).isEqualTo(0); + } + + @Test + public void onAnimationend_updatePositionForSharedPreference() { + final float percentageX = 0.0f; + final float percentageY = 0.5f; + + mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); + final String positionString = Prefs.getString(mContext, + Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null); + final Position position = Position.fromString(positionString); + + assertThat(position.getPercentageX()).isEqualTo(percentageX); + assertThat(position.getPercentageY()).isEqualTo(percentageY); + } + + @Test + public void onEdgeChangedIfNeeded_moveToLeftEdge_matchRadii() { + final Rect draggableBounds = mStubMenuViewAppearance.getMenuDraggableBounds(); + mMenuView.setTranslationX(draggableBounds.right); + + mMenuView.setTranslationX(draggableBounds.left); + mMenuView.onEdgeChangedIfNeeded(); + final float[] radii = getMenuViewGradient().getCornerRadii(); + + assertThat(radii[0]).isEqualTo(0.0f); + assertThat(radii[1]).isEqualTo(0.0f); + assertThat(radii[6]).isEqualTo(0.0f); + assertThat(radii[7]).isEqualTo(0.0f); + } + + private InstantInsetLayerDrawable getMenuViewInsetLayer() { + return (InstantInsetLayerDrawable) mMenuView.getBackground(); + } + + private GradientDrawable getMenuViewGradient() { + return (GradientDrawable) getMenuViewInsetLayer().getDrawable(INDEX_MENU_ITEM); + } + @After public void tearDown() throws Exception { mUiModeManager.setNightMode(mNightMode); + Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition); } } |