diff --git a/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml b/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml
new file mode 100644
index 0000000000000..de83df4e625c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index dc2bee56373c3..16152f80308af 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -99,6 +99,8 @@
#B3000000
+ @*android:color/background_material_dark
+ @*android:color/primary_text_default_material_dark
@color/material_dynamic_secondary20
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 9e8bef06270b6..55b59b63c2f9e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -219,6 +219,8 @@
#CCFFFFFF
#26FFFFFF
+ @*android:color/background_material_light
+ @*android:color/primary_text_default_material_light
#33FFFFFF
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 66f0e7543469b..f02f29a4f7414 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1336,6 +1336,14 @@
35dp
35dp
+ 15dp
+ 8dp
+ 8dp
+ 5dp
+ 14sp
+ 312dp
+ 48dp
+
8dp
16dp
-2dp
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 44031bb99751e..d5d99ccc99171 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2197,6 +2197,15 @@
Tap to open accessibility features. Customize or replace this button in Settings.\n\nView settings
Move button to the edge to hide it temporarily
+
+ Undo
+
+
+ {count, plural,
+ =1 {{label} shortcut removed}
+ other {# shortcuts removed}
+ }
+
Move top left
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index ea334b27fa09b..777d10c7acfd0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -28,6 +28,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.view.Display;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.MainThread;
@@ -56,6 +57,7 @@ public class AccessibilityFloatingMenuController implements
private Context mContext;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
+ private final AccessibilityManager mAccessibilityManager;
private final FeatureFlags mFeatureFlags;
@VisibleForTesting
IAccessibilityFloatingMenu mFloatingMenu;
@@ -96,6 +98,7 @@ public class AccessibilityFloatingMenuController implements
public AccessibilityFloatingMenuController(Context context,
WindowManager windowManager,
DisplayManager displayManager,
+ AccessibilityManager accessibilityManager,
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -103,6 +106,7 @@ public class AccessibilityFloatingMenuController implements
mContext = context;
mWindowManager = windowManager;
mDisplayManager = displayManager;
+ mAccessibilityManager = accessibilityManager;
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -180,7 +184,8 @@ public class AccessibilityFloatingMenuController implements
final Display defaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
mFloatingMenu = new MenuViewLayerController(
mContext.createWindowContext(defaultDisplay,
- TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager);
+ TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager,
+ mAccessibilityManager);
} else {
mFloatingMenu = new AccessibilityFloatingMenu(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
new file mode 100644
index 0000000000000..f56a15427452a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -0,0 +1,175 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
+/**
+ * Controls the interaction between {@link MagnetizedObject} and
+ * {@link MagnetizedObject.MagneticTarget}.
+ */
+class DismissAnimationController implements ComponentCallbacks {
+ private static final float COMPLETELY_OPAQUE = 1.0f;
+ private static final float COMPLETELY_TRANSPARENT = 0.0f;
+ private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
+ private static final float ANIMATING_MAX_ALPHA = 0.7f;
+
+ private final DismissView mDismissView;
+ private final MenuView mMenuView;
+ private final ValueAnimator mDismissAnimator;
+ private final MagnetizedObject> mMagnetizedObject;
+ private float mMinDismissSize;
+ private float mSizePercent;
+
+ DismissAnimationController(DismissView dismissView, MenuView menuView) {
+ mDismissView = dismissView;
+ mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
+ mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
+ mMenuView = menuView;
+
+ updateResources();
+
+ mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+ mDismissAnimator.addUpdateListener(dismissAnimation -> {
+ final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+ final float scaleValue = Math.max(animatedValue, mSizePercent);
+ dismissView.getCircle().setScaleX(scaleValue);
+ dismissView.getCircle().setScaleY(scaleValue);
+
+ menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ });
+
+ mDismissAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ super.onAnimationEnd(animation, isReverse);
+
+ if (isReverse) {
+ mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+ mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+ mMenuView.setAlpha(COMPLETELY_OPAQUE);
+ }
+ }
+ });
+
+ mMagnetizedObject =
+ new MagnetizedObject(mMenuView.getContext(), mMenuView,
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_X),
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_Y)) {
+ @Override
+ public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+
+ @Override
+ public float getHeight(MenuView underlyingObject) {
+ return underlyingObject.getHeight();
+ }
+
+ @Override
+ public float getWidth(MenuView underlyingObject) {
+ return underlyingObject.getWidth();
+ }
+ };
+
+ final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
+ dismissView.getCircle(), (int) mMinDismissSize);
+ mMagnetizedObject.addTarget(magneticTarget);
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ updateResources();
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing
+ }
+
+ void showDismissView(boolean show) {
+ if (show) {
+ mDismissView.show();
+ } else {
+ mDismissView.hide();
+ }
+ }
+
+ void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
+ mMagnetizedObject.setMagnetListener(magnetListener);
+ }
+
+ void maybeConsumeDownMotionEvent(MotionEvent event) {
+ mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
+ * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ *
+ * @param event that move the magnetized object which is also the menu list view.
+ * @return true if the location of the motion events moves within the magnetic field of a
+ * target, but false if didn't set
+ * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ */
+ boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
+ return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * This used to pass {@link MotionEvent#ACTION_UP} to the magnetized object to check if it was
+ * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ *
+ * @param event that move the magnetized object which is also the menu list view.
+ * @return true if the location of the motion events moves within the magnetic field of a
+ * target, but false if didn't set
+ * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ */
+ boolean maybeConsumeUpMotionEvent(MotionEvent event) {
+ return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ void animateDismissMenu(boolean scaleUp) {
+ if (scaleUp) {
+ mDismissAnimator.start();
+ } else {
+ mDismissAnimator.reverse();
+ }
+ }
+
+ private void updateResources() {
+ final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_size);
+ mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_small);
+ mSizePercent = mMinDismissSize / maxDismissSize;
+ }
+}
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 d6d0399035054..eb252323907d4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -47,6 +47,9 @@ class MenuAnimationController {
private static final float MIN_PERCENT = 0.0f;
private static final float MAX_PERCENT = 1.0f;
private static final float COMPLETELY_OPAQUE = 1.0f;
+ private static final float COMPLETELY_TRANSPARENT = 0.0f;
+ private static final float SCALE_SHRINK = 0.0f;
+ private static final float SCALE_GROW = 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;
@@ -297,6 +300,28 @@ class MenuAnimationController {
mMenuView.onDraggingStart();
}
+ void startShrinkAnimation(Runnable endAction) {
+ mMenuView.animate().cancel();
+
+ mMenuView.animate()
+ .scaleX(SCALE_SHRINK)
+ .scaleY(SCALE_SHRINK)
+ .alpha(COMPLETELY_TRANSPARENT)
+ .translationY(mMenuView.getTranslationY())
+ .withEndAction(endAction).start();
+ }
+
+ void startGrowAnimation() {
+ mMenuView.animate().cancel();
+
+ mMenuView.animate()
+ .scaleX(SCALE_GROW)
+ .scaleY(SCALE_GROW)
+ .alpha(COMPLETELY_OPAQUE)
+ .translationY(mMenuView.getTranslationY())
+ .start();
+ }
+
private void onSpringAnimationEnd(PointF position) {
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
constrainPositionAndUpdate(position);
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 3146c9f0d2af4..bc3cf0a6bab03 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -38,9 +38,12 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
private final PointF mMenuTranslationDown = new PointF();
private boolean mIsDragging = false;
private float mTouchSlop;
+ private final DismissAnimationController mDismissAnimationController;
- MenuListViewTouchHandler(MenuAnimationController menuAnimationController) {
+ MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
+ DismissAnimationController dismissAnimationController) {
mMenuAnimationController = menuAnimationController;
+ mDismissAnimationController = dismissAnimationController;
}
@Override
@@ -61,6 +64,7 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
mMenuAnimationController.cancelAnimations();
+ mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) {
@@ -69,8 +73,13 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuAnimationController.onDraggingStart();
}
- mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
- mMenuAnimationController.moveToPositionYIfNeeded(mMenuTranslationDown.y + dy);
+ mDismissAnimationController.showDismissView(/* show= */ true);
+
+ if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+ mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
+ mMenuAnimationController.moveToPositionYIfNeeded(
+ mMenuTranslationDown.y + dy);
+ }
}
break;
case MotionEvent.ACTION_UP:
@@ -79,10 +88,18 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
final float endX = mMenuTranslationDown.x + dx;
mIsDragging = false;
- if (!mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ mDismissAnimationController.showDismissView(/* show= */ false);
+ mMenuAnimationController.fadeOutIfEnabled();
+
+ return true;
+ }
+
+ if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ mDismissAnimationController.showDismissView(/* show= */ false);
}
// Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
new file mode 100644
index 0000000000000..9875ad06f1ed5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -0,0 +1,162 @@
+/*
+ * 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.util.TypedValue.COMPLEX_UNIT_PX;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The message view with the action prompt to whether to undo operation for users when removing
+ * the {@link MenuView}.
+ */
+class MenuMessageView extends LinearLayout implements
+ ViewTreeObserver.OnComputeInternalInsetsListener {
+ private final TextView mTextView;
+ private final Button mUndoButton;
+
+ @IntDef({
+ Index.TEXT_VIEW,
+ Index.UNDO_BUTTON
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Index {
+ int TEXT_VIEW = 0;
+ int UNDO_BUTTON = 1;
+ }
+
+ MenuMessageView(Context context) {
+ super(context);
+
+ setVisibility(GONE);
+
+ mTextView = new TextView(context);
+ mUndoButton = new Button(context);
+
+ addView(mTextView, Index.TEXT_VIEW,
+ new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
+ addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateResources();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(WRAP_CONTENT,
+ WRAP_CONTENT);
+ containerParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ setLayoutParams(containerParams);
+ setGravity(Gravity.CENTER_VERTICAL);
+
+ mUndoButton.setBackground(null);
+
+ updateResources();
+
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+ if (getVisibility() == VISIBLE) {
+ final int x = (int) getX();
+ final int y = (int) getY();
+ inoutInfo.touchableRegion.union(new Rect(x, y, x + getWidth(), y + getHeight()));
+ }
+ }
+
+ /**
+ * Registers a listener to be invoked when this undo action button is clicked. It should be
+ * called after {@link View#onAttachedToWindow()}.
+ *
+ * @param listener The listener that will run
+ */
+ void setUndoListener(OnClickListener listener) {
+ mUndoButton.setOnClickListener(listener);
+ }
+
+ private void updateResources() {
+ final Resources res = getResources();
+
+ final int containerPadding =
+ res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_container_horizontal_padding);
+ final int margin = res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_margin);
+ final FrameLayout.LayoutParams containerParams =
+ (FrameLayout.LayoutParams) getLayoutParams();
+ containerParams.setMargins(margin, margin, margin, margin);
+ setLayoutParams(containerParams);
+ setBackground(res.getDrawable(R.drawable.accessibility_floating_message_background));
+ setPadding(containerPadding, /* top= */ 0, containerPadding, /* bottom= */ 0);
+ setMinimumWidth(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_width));
+ setMinimumHeight(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_height));
+ setElevation(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_elevation));
+
+ final int textPadding =
+ res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_text_vertical_padding);
+ final int textColor = res.getColor(R.color.accessibility_floating_menu_message_text);
+ final int textSize = res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_text_size);
+ mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
+ mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
+ mTextView.setTextColor(textColor);
+
+ final ColorStateList colorAccent = Utils.getColorAccent(getContext());
+ mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
+ mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize);
+ mUndoButton.setTextColor(colorAccent);
+ }
+}
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 15d139cf15da5..6a14af52fbaff 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -42,7 +42,7 @@ import java.util.Collections;
import java.util.List;
/**
- * The menu view displays the accessibility features.
+ * The container view displays the accessibility features.
*/
@SuppressLint("ViewConstructor")
class MenuView extends FrameLayout implements
@@ -64,13 +64,14 @@ class MenuView extends FrameLayout implements
this::onTargetFeaturesChanged;
private final MenuViewAppearance mMenuViewAppearance;
+ private OnTargetFeaturesChangeListener mFeaturesChangeListener;
+
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
super(context);
mMenuViewModel = menuViewModel;
mMenuViewAppearance = menuViewAppearance;
mMenuAnimationController = new MenuAnimationController(this);
-
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
@@ -96,7 +97,9 @@ class MenuView extends FrameLayout implements
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(mBoundsInParent);
+ if (getVisibility() == VISIBLE) {
+ inoutInfo.touchableRegion.union(mBoundsInParent);
+ }
}
@Override
@@ -108,10 +111,18 @@ class MenuView extends FrameLayout implements
mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
}
+ void setOnTargetFeaturesChangeListener(OnTargetFeaturesChangeListener listener) {
+ mFeaturesChangeListener = listener;
+ }
+
void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) {
mTargetFeaturesView.addOnItemTouchListener(listener);
}
+ MenuAnimationController getMenuAnimationController() {
+ return mMenuAnimationController;
+ }
+
@SuppressLint("NotifyDataSetChanged")
private void onItemSizeChanged() {
mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
@@ -139,7 +150,7 @@ class MenuView extends FrameLayout implements
onEdgeChanged();
}
- private void onEdgeChanged() {
+ void onEdgeChanged() {
final int[] insets = mMenuViewAppearance.getMenuInsets();
getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2],
insets[3]);
@@ -193,6 +204,9 @@ class MenuView extends FrameLayout implements
onEdgeChanged();
onPositionChanged();
+ if (mFeaturesChangeListener != null) {
+ mFeaturesChangeListener.onChange(newTargetFeatures);
+ }
mMenuAnimationController.fadeOutIfEnabled();
}
@@ -299,4 +313,17 @@ class MenuView extends FrameLayout implements
final ViewGroup parentView = (ViewGroup) getParent();
parentView.setSystemGestureExclusionRects(Collections.singletonList(mBoundsInParent));
}
+
+ /**
+ * Interface definition for the {@link AccessibilityTarget} list changes.
+ */
+ interface OnTargetFeaturesChangeListener {
+ /**
+ * Called when the list of accessibility target features was updated. This will be
+ * invoked when the end of {@code onTargetFeaturesChanged}.
+ *
+ * @param newTargetFeatures the list related to the current accessibility features.
+ */
+ void onChange(List newTargetFeatures);
+ }
}
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 5252519e9faf4..3e620a26f42c5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,42 +16,154 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
- * The basic interactions with the child view {@link MenuView}.
+ * The basic interactions with the child views {@link MenuView}, {@link DismissView}, and
+ * {@link MenuMessageView}. When dragging the menu view, the dismissed view would be shown at the
+ * same time. If the menu view overlaps on the dismissed circle view and drops out, the menu
+ * message view would be shown and allowed users to undo it.
*/
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout {
+ private static final int SHOW_MESSAGE_DELAY_MS = 3000;
+
private final MenuView mMenuView;
+ private final MenuMessageView mMessageView;
+ private final DismissView mDismissView;
+ private final MenuAnimationController mMenuAnimationController;
+ private final AccessibilityManager mAccessibilityManager;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final IAccessibilityFloatingMenu mFloatingMenu;
+ private final DismissAnimationController mDismissAnimationController;
@IntDef({
- LayerIndex.MENU_VIEW
+ LayerIndex.MENU_VIEW,
+ LayerIndex.DISMISS_VIEW,
+ LayerIndex.MESSAGE_VIEW,
})
@Retention(RetentionPolicy.SOURCE)
@interface LayerIndex {
int MENU_VIEW = 0;
+ int DISMISS_VIEW = 1;
+ int MESSAGE_VIEW = 2;
}
- MenuViewLayer(@NonNull Context context, WindowManager windowManager) {
+ @VisibleForTesting
+ final Runnable mDismissMenuAction = new Runnable() {
+ @Override
+ public void run() {
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+ mFloatingMenu.hide();
+ }
+ };
+
+ MenuViewLayer(@NonNull Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
super(context);
+ mAccessibilityManager = accessibilityManager;
+ mFloatingMenu = floatingMenu;
+
final MenuViewModel menuViewModel = new MenuViewModel(context);
final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
windowManager);
mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
+ mMenuAnimationController = mMenuView.getMenuAnimationController();
+
+ mDismissView = new DismissView(context);
+ mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
+ mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velocityX, float velocityY, boolean wasFlungOut) {
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ hideMenuAndShowMessage();
+ mDismissView.hide();
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ }
+ });
+
+ final MenuListViewTouchHandler menuListViewTouchHandler = new MenuListViewTouchHandler(
+ mMenuAnimationController, mDismissAnimationController);
+ mMenuView.addOnItemTouchListenerToList(menuListViewTouchHandler);
+
+ mMessageView = new MenuMessageView(context);
+
+ mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
+ if (newTargetFeatures.size() < 1) {
+ return;
+ }
+
+ // During the undo action period, the pending action will be canceled and undo back
+ // to the previous state if users did any action related to the accessibility features.
+ if (mMessageView.getVisibility() == VISIBLE) {
+ undo();
+ }
+
+ final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
+ messageText.setText(getMessageText(newTargetFeatures));
+ });
addView(mMenuView, LayerIndex.MENU_VIEW);
+ addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ addView(mMessageView, LayerIndex.MESSAGE_VIEW);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDismissView.updateResources();
+ }
+
+ private String getMessageText(List newTargetFeatures) {
+ Preconditions.checkArgument(newTargetFeatures.size() > 0,
+ "The list should at least have one feature.");
+
+ final Map arguments = new HashMap<>();
+ arguments.put("count", newTargetFeatures.size());
+ arguments.put("label", newTargetFeatures.get(0).getLabel());
+ return PluralsMessageFormatter.format(getResources(), arguments,
+ R.string.accessibility_floating_button_undo_message_text);
}
@Override
@@ -68,6 +180,8 @@ class MenuViewLayer extends FrameLayout {
super.onAttachedToWindow();
mMenuView.show();
+ mMessageView.setUndoListener(view -> undo());
+ mContext.registerComponentCallbacks(mDismissAnimationController);
}
@Override
@@ -75,5 +189,26 @@ class MenuViewLayer extends FrameLayout {
super.onDetachedFromWindow();
mMenuView.hide();
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ mContext.unregisterComponentCallbacks(mDismissAnimationController);
+ }
+
+ private void hideMenuAndShowMessage() {
+ final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
+ SHOW_MESSAGE_DELAY_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
+ | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ mHandler.postDelayed(mDismissMenuAction, delayTime);
+ mMessageView.setVisibility(VISIBLE);
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
+ }
+
+ private void undo() {
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ mMessageView.setVisibility(GONE);
+ mMenuView.onEdgeChanged();
+ mMenuView.onPositionChanged();
+ mMenuView.setVisibility(VISIBLE);
+ mMenuAnimationController.startGrowAnimation();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index d2093c200ca2e..b1a64eda46ff7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.graphics.PixelFormat;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
/**
* Controls the {@link MenuViewLayer} whether to be attached to the window via the interface
@@ -32,9 +33,10 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
private final MenuViewLayer mMenuViewLayer;
private boolean mIsShowing;
- MenuViewLayerController(Context context, WindowManager windowManager) {
+ MenuViewLayerController(Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context, windowManager);
+ mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 19a6c66652ddd..77d38c58e6857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -35,6 +35,7 @@ import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -68,6 +69,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
public MockitoRule mockito = MockitoJUnit.rule();
private Context mContextWrapper;
+ private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
private AccessibilityButtonTargetsObserver mTargetsObserver;
@@ -87,6 +89,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
}
};
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
@@ -348,8 +351,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
- displayManager, mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor,
- featureFlags);
+ displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
+ mKeyguardUpdateMonitor, featureFlags);
controller.init();
return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
new file mode 100644
index 0000000000000..8ef65dcb2c3a1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.bubbles.DismissView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link DismissAnimationController}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DismissAnimationControllerTest extends SysuiTestCase {
+ private DismissAnimationController mDismissAnimationController;
+ private DismissView mDismissView;
+
+ @Before
+ public void setUp() throws Exception {
+ final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
+ stubWindowManager);
+ final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
+ stubMenuViewAppearance);
+ mDismissView = new DismissView(mContext);
+ mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+ }
+
+ @Test
+ public void showDismissView_success() {
+ mDismissAnimationController.showDismissView(true);
+
+ verify(mDismissView).show();
+ }
+
+ @Test
+ public void hideDismissView_success() {
+ mDismissAnimationController.showDismissView(false);
+
+ verify(mDismissView).hide();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index dbf291c49ee51..d0bd4f7026ebc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -18,9 +18,16 @@ package com.android.systemui.accessibility.floatingmenu;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
import android.graphics.PointF;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -36,6 +43,8 @@ import org.junit.runner.RunWith;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class MenuAnimationControllerTest extends SysuiTestCase {
+
+ private ViewPropertyAnimator mViewPropertyAnimator;
private MenuView mMenuView;
private MenuAnimationController mMenuAnimationController;
@@ -45,7 +54,11 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
- mMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mViewPropertyAnimator = spy(mMenuView.animate());
+ doReturn(mViewPropertyAnimator).when(mMenuView).animate();
+
mMenuAnimationController = new MenuAnimationController(mMenuView);
}
@@ -58,4 +71,20 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
assertThat(mMenuView.getTranslationX()).isEqualTo(50);
assertThat(mMenuView.getTranslationY()).isEqualTo(60);
}
+
+ @Test
+ public void startShrinkAnimation_verifyAnimationEndAction() {
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(View.VISIBLE));
+
+ verify(mViewPropertyAnimator).withEndAction(any(Runnable.class));
+ }
+
+ @Test
+ public void startGrowAnimation_menuCompletelyOpaque() {
+ mMenuAnimationController.startShrinkAnimation(null);
+
+ mMenuAnimationController.startGrowAnimation();
+
+ assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f);
+ }
}
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 c5b9a294fc341..4acb394bee95d 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
@@ -21,6 +21,8 @@ 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.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -36,6 +38,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.wm.shell.bubbles.DismissView;
import org.junit.After;
import org.junit.Before;
@@ -57,7 +60,9 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
private MenuView mStubMenuView;
private MenuListViewTouchHandler mTouchHandler;
private MenuAnimationController mMenuAnimationController;
+ private DismissAnimationController mDismissAnimationController;
private RecyclerView mStubListView;
+ private DismissView mDismissView;
@Before
public void setUp() throws Exception {
@@ -69,7 +74,11 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
mStubMenuView.setTranslationX(0);
mStubMenuView.setTranslationY(0);
mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
- mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController);
+ mDismissView = spy(new DismissView(mContext));
+ mDismissAnimationController =
+ spy(new DismissAnimationController(mDismissView, mStubMenuView));
+ mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
+ mDismissAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
@@ -88,7 +97,9 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
}
@Test
- public void onActionMoveEvent_shouldMoveToPosition() {
+ public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
+ doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+ any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
@@ -107,6 +118,24 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
assertThat(mStubMenuView.getTranslationY()).isEqualTo(offset);
}
+ @Test
+ public void onActionMoveEvent_shouldShowDismissView() {
+ 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);
+
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+ verify(mDismissView).show();
+ }
+
@Test
public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
final int offset = 100;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 8c8d6aca7cd7f..dd7ce0e06c326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -34,6 +34,7 @@ import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -58,6 +59,9 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Mock
private WindowMetrics mWindowMetrics;
@@ -72,7 +76,8 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
- mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager);
+ mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager,
+ mAccessibilityManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 23c6ef1338b3f..d20eeafde09c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -23,18 +23,25 @@ import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.Laye
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/** Tests for {@link MenuViewLayer}. */
@RunWith(AndroidTestingRunner.class)
@@ -43,10 +50,19 @@ import org.junit.runner.RunWith;
public class MenuViewLayerTest extends SysuiTestCase {
private MenuViewLayer mMenuViewLayer;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IAccessibilityFloatingMenu mFloatingMenu;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager);
+ final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
+ AccessibilityManager.class);
+ mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+ mFloatingMenu);
}
@Test
@@ -64,4 +80,11 @@ public class MenuViewLayerTest extends SysuiTestCase {
assertThat(menuView.getVisibility()).isEqualTo(GONE);
}
+
+ @Test
+ public void tiggerDismissMenuAction_hideFloatingMenu() {
+ mMenuViewLayer.mDismissMenuAction.run();
+
+ verify(mFloatingMenu).hide();
+ }
}