Create a DragToInteractView derived from DismissView, which can feature multiple views to drag to.
Bug: 297583708
Test: atest com.android.systemui.accessibility.floatingmenu
Flag: ACONFIG com.android.systemui.flags.Flags.floating_menu_drag_to_edit ENABLED
Change-Id: I93755e3dd5f0bb441b52935e8be98db6604cfc6d
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 7ba889b..866aa89 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -17,6 +17,13 @@
}
flag {
+ name: "floating_menu_drag_to_edit"
+ namespace: "accessibility"
+ description: "adds a second drag button to allow the user edit the shortcut."
+ bug: "297583708"
+}
+
+flag {
name: "floating_menu_ime_displacement_animation"
namespace: "accessibility"
description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index ec4c7d5..f107709b 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -184,6 +184,7 @@
<item type="id" name="action_move_to_edge_and_hide"/>
<item type="id" name="action_move_out_edge_and_show"/>
<item type="id" name="action_remove_menu"/>
+ <item type="id" name="action_edit"/>
<!-- rounded corner view id -->
<item type="id" name="rounded_corner_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1989589..4b449de 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2556,6 +2556,8 @@
<string name="accessibility_floating_button_action_remove_menu">Remove</string>
<!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
+ <!-- Action in accessibility menu to open the shortcut edit menu" [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_action_edit">Edit</string>
<!-- Device Controls strings -->
<!-- Device Controls, Quick Settings tile title [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 568b24d..7fd72ec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -16,127 +16,138 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.util.ArrayMap;
+import android.util.Pair;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Flags;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import java.util.Map;
+import java.util.Objects;
+
/**
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
class DragToInteractAnimationController {
- private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
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 DragToInteractView mInteractView;
private final DismissView mDismissView;
private final MenuView mMenuView;
- private final ValueAnimator mDismissAnimator;
- private final MagnetizedObject<?> mMagnetizedObject;
- private float mMinDismissSize;
+
+ /**
+ * MagnetizedObject cannot differentiate between its MagnetizedTargets,
+ * so we need an object & an animator for every interactable.
+ */
+ private final ArrayMap<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> mInteractMap;
+
+ private float mMinInteractSize;
private float mSizePercent;
+ DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) {
+ mDismissView = null;
+ mInteractView = interactView;
+ mInteractView.setPivotX(interactView.getWidth() / 2.0f);
+ mInteractView.setPivotY(interactView.getHeight() / 2.0f);
+ mMenuView = menuView;
+
+ updateResources();
+
+ mInteractMap = new ArrayMap<>();
+ interactView.getInteractMap().forEach((viewId, pair) -> {
+ DismissCircleView circleView = pair.getFirst();
+ createMagnetizedObjectAndAnimator(circleView);
+ });
+ }
+
DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
mDismissView = dismissView;
+ mInteractView = null;
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<MenuView>(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);
- mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU);
+ mInteractMap = new ArrayMap<>();
+ createMagnetizedObjectAndAnimator(dismissView.getCircle());
}
- void showDismissView(boolean show) {
- if (show) {
- mDismissView.show();
- } else {
- mDismissView.hide();
+ void showInteractView(boolean show) {
+ if (Flags.floatingMenuDragToEdit() && mInteractView != null) {
+ if (show) {
+ mInteractView.show();
+ } else {
+ mInteractView.hide();
+ }
+ } else if (mDismissView != null) {
+ if (show) {
+ mDismissView.show();
+ } else {
+ mDismissView.hide();
+ }
}
}
void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
- mMagnetizedObject.setMagnetListener(magnetListener);
+ mInteractMap.forEach((viewId, pair) -> {
+ MagnetizedObject<?> magnetizedObject = pair.first;
+ magnetizedObject.setMagnetListener(magnetListener);
+ });
}
@VisibleForTesting
- MagnetizedObject.MagnetListener getMagnetListener() {
- return mMagnetizedObject.getMagnetListener();
+ MagnetizedObject.MagnetListener getMagnetListener(int id) {
+ return Objects.requireNonNull(mInteractMap.get(id)).first.getMagnetListener();
}
void maybeConsumeDownMotionEvent(MotionEvent event) {
- mMagnetizedObject.maybeConsumeMotionEvent(event);
+ mInteractMap.forEach((viewId, pair) -> {
+ MagnetizedObject<?> magnetizedObject = pair.first;
+ magnetizedObject.maybeConsumeMotionEvent(event);
+ });
+ }
+
+ private int maybeConsumeMotionEvent(MotionEvent event) {
+ for (Map.Entry<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> set:
+ mInteractMap.entrySet()) {
+ MagnetizedObject<MenuView> magnetizedObject = set.getValue().first;
+ if (magnetizedObject.maybeConsumeMotionEvent(event)) {
+ return set.getKey();
+ }
+ }
+ return empty;
}
/**
- * 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}.
+ * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized objects
+ * to check if it was within a 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
+ * @return id of a target if the location of the motion events moves
+ * within the field of the target, otherwise it returns{@link android.R.id#empty}.
+ * <p>
* {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
- boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
- return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ int maybeConsumeMoveMotionEvent(MotionEvent event) {
+ return maybeConsumeMotionEvent(event);
}
/**
@@ -144,31 +155,93 @@
* 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
+ * @return id of a target if the location of the motion events moves
+ * within the field of the target, otherwise it returns{@link android.R.id#empty}.
* {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
- boolean maybeConsumeUpMotionEvent(MotionEvent event) {
- return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ int maybeConsumeUpMotionEvent(MotionEvent event) {
+ return maybeConsumeMotionEvent(event);
}
- void animateDismissMenu(boolean scaleUp) {
+ void animateInteractMenu(int targetViewId, boolean scaleUp) {
+ Pair<MagnetizedObject<MenuView>, ValueAnimator> value = mInteractMap.get(targetViewId);
+ if (value == null) {
+ return;
+ }
+ ValueAnimator animator = value.second;
if (scaleUp) {
- mDismissAnimator.start();
+ animator.start();
} else {
- mDismissAnimator.reverse();
+ animator.reverse();
}
}
void updateResources() {
- final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ final float maxInteractSize = mMenuView.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.dismiss_circle_size);
- mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ mMinInteractSize = mMenuView.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.dismiss_circle_small);
- mSizePercent = mMinDismissSize / maxDismissSize;
+ mSizePercent = mMinInteractSize / maxInteractSize;
}
- interface DismissCallback {
- void onDismiss();
+ /**
+ * Creates a magnetizedObject & valueAnimator pair for the provided circleView,
+ * and adds them to the interactMap.
+ *
+ * @param circleView circleView to create objects for.
+ */
+ private void createMagnetizedObjectAndAnimator(DismissCircleView circleView) {
+ MagnetizedObject<MenuView> magnetizedObject = new MagnetizedObject<MenuView>(
+ mMenuView.getContext(), mMenuView,
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_X),
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_Y)) {
+ @Override
+ public void getLocationOnScreen(MenuView underlyingObject, @NonNull int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+
+ @Override
+ public float getHeight(MenuView underlyingObject) {
+ return underlyingObject.getHeight();
+ }
+
+ @Override
+ public float getWidth(MenuView underlyingObject) {
+ return underlyingObject.getWidth();
+ }
+ };
+ // Avoid unintended selection of an object / option
+ magnetizedObject.setFlingToTargetEnabled(false);
+ magnetizedObject.addTarget(new MagnetizedObject.MagneticTarget(
+ circleView, (int) mMinInteractSize));
+
+ final ValueAnimator animator =
+ ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+
+ animator.addUpdateListener(dismissAnimation -> {
+ final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+ final float scaleValue = Math.max(animatedValue, mSizePercent);
+ circleView.setScaleX(scaleValue);
+ circleView.setScaleY(scaleValue);
+
+ mMenuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ super.onAnimationEnd(animation, isReverse);
+
+ if (isReverse) {
+ circleView.setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+ circleView.setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+ mMenuView.setAlpha(COMPLETELY_OPAQUE);
+ }
+ }
+ });
+
+ mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
new file mode 100644
index 0000000..0ef3d20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2023 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.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.ArrayMap
+import android.util.IntProperty
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.Space
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
+import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.common.bubbles.DismissCircleView
+import com.android.wm.shell.common.bubbles.DismissView
+
+/**
+ * View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
+ */
+class DragToInteractView(context: Context) : FrameLayout(context) {
+ /**
+ * The configuration is used to provide module specific resource ids
+ *
+ * @see [setup] method
+ */
+ data class Config(
+ /** dimen resource id of the dismiss target circle view size */
+ @DimenRes val targetSizeResId: Int,
+ /** dimen resource id of the icon size in the dismiss target */
+ @DimenRes val iconSizeResId: Int,
+ /** dimen resource id of the bottom margin for the dismiss target */
+ @DimenRes var bottomMarginResId: Int,
+ /** dimen resource id of the height for dismiss area gradient */
+ @DimenRes val floatingGradientHeightResId: Int,
+ /** color resource id of the dismiss area gradient color */
+ @ColorRes val floatingGradientColorResId: Int,
+ /** drawable resource id of the dismiss target background */
+ @DrawableRes val backgroundResId: Int,
+ /** drawable resource id of the icon for the dismiss target */
+ @DrawableRes val iconResId: Int
+ )
+
+ companion object {
+ private const val SHOULD_SETUP = "The view isn't ready. Should be called after `setup`"
+ private val TAG = DragToInteractView::class.simpleName
+ }
+
+ // START DragToInteractView modification
+ // We could technically access each DismissCircleView from their Animator,
+ // but the animators only store a weak reference to their targets. This is safer.
+ var interactMap = ArrayMap<Int, Pair<DismissCircleView, PhysicsAnimator<DismissCircleView>>>()
+ // END DragToInteractView modification
+ var isShowing = false
+ var config: Config? = null
+
+ private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
+ private val INTERACT_SCRIM_FADE_MS = 200L
+ private var wm: WindowManager =
+ context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private var gradientDrawable: GradientDrawable? = null
+
+ private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+ object : IntProperty<GradientDrawable>("alpha") {
+ override fun setValue(d: GradientDrawable, percent: Int) {
+ d.alpha = percent
+ }
+ override fun get(d: GradientDrawable): Int {
+ return d.alpha
+ }
+ }
+
+ init {
+ clipToPadding = false
+ clipChildren = false
+ visibility = View.INVISIBLE
+
+ // START DragToInteractView modification
+ // Resources included within implementation as we aren't concerned with decoupling them.
+ setup(
+ Config(
+ targetSizeResId = R.dimen.dismiss_circle_size,
+ iconSizeResId = R.dimen.dismiss_target_x_size,
+ bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+ floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+ floatingGradientColorResId = android.R.color.system_neutral1_900,
+ backgroundResId = R.drawable.dismiss_circle_background,
+ iconResId = R.drawable.pip_ic_close_white
+ )
+ )
+ // END DragToInteractView modification
+ }
+
+ /**
+ * Sets up view with the provided resource ids.
+ *
+ * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+ * with default params in module specific extension:
+ *
+ * @see [DismissView.setup] in DismissViewExt.kt
+ */
+ fun setup(config: Config) {
+ this.config = config
+
+ // Setup layout
+ layoutParams =
+ LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+ Gravity.BOTTOM
+ )
+ updatePadding()
+
+ // Setup gradient
+ gradientDrawable = createGradient(color = config.floatingGradientColorResId)
+ background = gradientDrawable
+
+ // START DragToInteractView modification
+
+ // Setup LinearLayout. Added to organize multiple circles.
+ val linearLayout = LinearLayout(context)
+ linearLayout.layoutParams =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ linearLayout.weightSum = 0f
+ addView(linearLayout)
+
+ // Setup DismissCircleView. Code block replaced with repeatable functions
+ addSpace(linearLayout)
+ addCircle(
+ config,
+ com.android.systemui.res.R.id.action_remove_menu,
+ R.drawable.pip_ic_close_white,
+ linearLayout
+ )
+ addCircle(
+ config,
+ com.android.systemui.res.R.id.action_edit,
+ com.android.systemui.res.R.drawable.ic_screenshot_edit,
+ linearLayout
+ )
+ // END DragToInteractView modification
+ }
+
+ /** Animates this view in. */
+ fun show() {
+ if (isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
+ isShowing = true
+ visibility = View.VISIBLE
+ val alphaAnim =
+ ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 255)
+ alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+ alphaAnim.start()
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator.cancel()
+ animator.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring).start()
+ }
+ // END DragToInteractView modification
+ }
+
+ /**
+ * Animates this view out, as well as the circle that encircles the bubbles, if they were
+ * dragged into the target and encircled.
+ */
+ fun hide() {
+ if (!isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
+ isShowing = false
+ val alphaAnim =
+ ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0)
+ alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+ alphaAnim.start()
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator
+ .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), spring)
+ .withEndActions({ visibility = View.INVISIBLE })
+ .start()
+ }
+ // END DragToInteractView modification
+ }
+
+ /** Cancels the animator for the dismiss target. */
+ fun cancelAnimators() {
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator.cancel()
+ }
+ // END DragToInteractView modification
+ }
+
+ fun updateResources() {
+ val config = checkExists(config) ?: return
+ updatePadding()
+ layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+ val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val circle = it.value.first
+ circle.layoutParams.width = targetSize
+ circle.layoutParams.height = targetSize
+ circle.requestLayout()
+ }
+ // END DragToInteractView modification
+ }
+
+ private fun createGradient(@ColorRes color: Int): GradientDrawable {
+ val gradientColor = ContextCompat.getColor(context, color)
+ val alpha = 0.7f * 255
+ val gradientColorWithAlpha =
+ Color.argb(
+ alpha.toInt(),
+ Color.red(gradientColor),
+ Color.green(gradientColor),
+ Color.blue(gradientColor)
+ )
+ val gd =
+ GradientDrawable(
+ GradientDrawable.Orientation.BOTTOM_TOP,
+ intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)
+ )
+ gd.setDither(true)
+ gd.alpha = 0
+ return gd
+ }
+
+ private fun updatePadding() {
+ val config = checkExists(config) ?: return
+ val insets: WindowInsets = wm.currentWindowMetrics.windowInsets
+ val navInset = insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
+ setPadding(
+ 0,
+ 0,
+ 0,
+ navInset.bottom + resources.getDimensionPixelSize(config.bottomMarginResId)
+ )
+ }
+
+ /**
+ * Checks if the value is set up and exists, if not logs an exception. Used for convenient
+ * logging in case `setup` wasn't called before
+ *
+ * @return value provided as argument
+ */
+ private fun <T> checkExists(value: T?): T? {
+ if (value == null) Log.e(TAG, SHOULD_SETUP)
+ return value
+ }
+
+ // START DragToInteractView modification
+ private fun addSpace(parent: LinearLayout) {
+ val space = Space(context)
+ // Spaces are weighted equally to space out circles evenly
+ space.layoutParams =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f
+ )
+ parent.addView(space)
+ parent.weightSum = parent.weightSum + 1f
+ }
+
+ private fun addCircle(config: Config, id: Int, iconResId: Int, parent: LinearLayout) {
+ val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+ val circleLayoutParams = LinearLayout.LayoutParams(targetSize, targetSize, 0f)
+ circleLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ val circle = DismissCircleView(context)
+ circle.id = id
+ circle.setup(config.backgroundResId, iconResId, config.iconSizeResId)
+ circle.layoutParams = circleLayoutParams
+
+ // Initial position with circle offscreen so it's animated up
+ circle.translationY =
+ resources.getDimensionPixelSize(config.floatingGradientHeightResId).toFloat()
+
+ interactMap[circle.id] = Pair(circle, PhysicsAnimator.getInstance(circle))
+ parent.addView(circle)
+ addSpace(parent)
+ }
+ // END DragToInteractView modification
+}
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 a270558..d3e85e0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -37,7 +37,6 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;
import java.util.HashMap;
@@ -73,7 +72,6 @@
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
- private DragToInteractAnimationController.DismissCallback mDismissCallback;
private Runnable mSpringAnimationsEndAction;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -170,11 +168,6 @@
mSpringAnimationsEndAction = runnable;
}
- void setDismissCallback(
- DragToInteractAnimationController.DismissCallback dismissCallback) {
- mDismissCallback = dismissCallback;
- }
-
void moveToTopLeftPosition() {
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -205,13 +198,6 @@
constrainPositionAndUpdate(position, /* writeToPosition = */ true);
}
- void removeMenu() {
- Preconditions.checkArgument(mDismissCallback != null,
- "The dismiss callback should be initialized first.");
-
- mDismissCallback.onDismiss();
- }
-
void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
final boolean shouldMenuFlingLeft = isOnLeftSide()
? velocityX < ESCAPE_VELOCITY
@@ -334,8 +320,6 @@
moveToEdgeAndHide();
return true;
}
-
- fadeOutIfEnabled();
return false;
}
@@ -453,8 +437,6 @@
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
constrainPositionAndUpdate(position, writeToPosition);
- fadeOutIfEnabled();
-
if (mSpringAnimationsEndAction != null) {
mSpringAnimationsEndAction.run();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index 9c22a77..975a602 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -27,6 +27,7 @@
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
/**
@@ -35,15 +36,18 @@
*/
class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate {
private final MenuAnimationController mAnimationController;
+ private final MenuViewLayer mMenuViewLayer;
MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
- MenuAnimationController animationController) {
+ MenuAnimationController animationController, MenuViewLayer menuViewLayer) {
super(recyclerViewDelegate);
mAnimationController = animationController;
+ mMenuViewLayer = menuViewLayer;
}
@Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ public void onInitializeAccessibilityNodeInfo(
+ @NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final Resources res = host.getResources();
@@ -90,6 +94,15 @@
R.id.action_remove_menu,
res.getString(R.string.accessibility_floating_button_action_remove_menu));
info.addAction(removeMenu);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat edit =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_edit,
+ res.getString(
+ R.string.accessibility_floating_button_action_remove_menu));
+ info.addAction(edit);
+ }
}
@Override
@@ -132,8 +145,8 @@
return true;
}
- if (action == R.id.action_remove_menu) {
- mAnimationController.removeMenu();
+ if (action == R.id.action_remove_menu || action == R.id.action_edit) {
+ mMenuViewLayer.dispatchAccessibilityAction(action);
return true;
}
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 52e7b91..7519168 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
+
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -78,10 +80,9 @@
mMenuAnimationController.onDraggingStart();
}
- mDragToInteractAnimationController.showDismissView(/* show= */ true);
-
- if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
- motionEvent)) {
+ mDragToInteractAnimationController.showInteractView(/* show= */ true);
+ if (mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(motionEvent)
+ == empty) {
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
mMenuAnimationController.moveToPositionYIfNeeded(
mMenuTranslationDown.y + dy);
@@ -94,21 +95,19 @@
final float endX = mMenuTranslationDown.x + dx;
mIsDragging = false;
- if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
- mDragToInteractAnimationController.showDismissView(/* show= */ false);
- mMenuAnimationController.fadeOutIfEnabled();
+ mDragToInteractAnimationController.showInteractView(/* show= */ false);
+ if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ mMenuAnimationController.fadeOutIfEnabled();
return true;
}
- if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
- motionEvent)) {
+ if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent)
+ == empty) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- mDragToInteractAnimationController.showDismissView(/* show= */ false);
}
-
// 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 76808cb..334cc87 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,24 +21,28 @@
import android.annotation.SuppressLint;
import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
-import androidx.core.view.AccessibilityDelegateCompat;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.Flags;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.Collections;
@@ -72,26 +76,20 @@
private final MenuAnimationController mMenuAnimationController;
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
private OnMoveToTuckedListener mMoveToTuckedListener;
+ private SecureSettings mSecureSettings;
- MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
+ MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance,
+ SecureSettings secureSettings) {
super(context);
mMenuViewModel = menuViewModel;
mMenuViewAppearance = menuViewAppearance;
+ mSecureSettings = secureSettings;
mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance);
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context));
- mTargetFeaturesView.setAccessibilityDelegateCompat(
- new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) {
- @NonNull
- @Override
- public AccessibilityDelegateCompat getItemDelegate() {
- return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
- mMenuAnimationController);
- }
- });
setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
// Avoid drawing out of bounds of the parent view
setClipToOutline(true);
@@ -278,6 +276,7 @@
if (mFeaturesChangeListener != null) {
mFeaturesChangeListener.onChange(newTargetFeatures);
}
+
mMenuAnimationController.fadeOutIfEnabled();
}
@@ -306,6 +305,10 @@
return mMenuViewAppearance.getMenuPosition();
}
+ RecyclerView getTargetFeaturesView() {
+ return mTargetFeaturesView;
+ }
+
void persistPositionAndUpdateEdge(Position percentagePosition) {
mMenuViewModel.updateMenuSavingPosition(percentagePosition);
mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -424,6 +427,35 @@
onPositionChanged();
}
+ void gotoEditScreen() {
+ if (!Flags.floatingMenuDragToEdit()) {
+ return;
+ }
+ mMenuAnimationController.flingMenuThenSpringToEdge(
+ getMenuPosition().x, 100f, 0f);
+ mContext.startActivity(getIntentForEditScreen());
+ }
+
+ Intent getIntentForEditScreen() {
+ List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+ mSecureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).stream().toList();
+
+ Intent intent = new Intent(
+ Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ Bundle args = new Bundle();
+ Bundle fragmentArgs = new Bundle();
+ fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+ args.putBundle(":settings:show_fragment_args", fragmentArgs);
+ // TODO: b/318748373 - The fragment should set its own title using the targets
+ args.putString(
+ ":settings:show_fragment_title", "Accessibility Shortcut");
+ intent.replaceExtras(args);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return intent;
+ }
+
private InstantInsetLayerDrawable getContainerViewInsetLayer() {
return (InstantInsetLayerDrawable) getBackground();
}
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 6869bba..4b99701 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -59,7 +59,10 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,6 +97,8 @@
private final MenuListViewTouchHandler mMenuListViewTouchHandler;
private final MenuMessageView mMessageView;
private final DismissView mDismissView;
+ private final DragToInteractView mDragToInteractView;
+
private final MenuViewAppearance mMenuViewAppearance;
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
@@ -178,7 +183,10 @@
};
MenuViewLayer(@NonNull Context context, WindowManager windowManager,
- AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu,
+ AccessibilityManager accessibilityManager,
+ MenuViewModel menuViewModel,
+ MenuViewAppearance menuViewAppearance, MenuView menuView,
+ IAccessibilityFloatingMenu floatingMenu,
SecureSettings secureSettings) {
super(context);
@@ -190,43 +198,52 @@
mFloatingMenu = floatingMenu;
mSecureSettings = secureSettings;
- mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings);
- mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
- mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
+ mMenuViewModel = menuViewModel;
+ mMenuViewAppearance = menuViewAppearance;
+ mMenuView = menuView;
+ RecyclerView targetFeaturesView = mMenuView.getTargetFeaturesView();
+ targetFeaturesView.setAccessibilityDelegateCompat(
+ new RecyclerViewAccessibilityDelegate(targetFeaturesView) {
+ @NonNull
+ @Override
+ public AccessibilityDelegateCompat getItemDelegate() {
+ return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
+ mMenuAnimationController, MenuViewLayer.this);
+ }
+ });
mMenuAnimationController = mMenuView.getMenuAnimationController();
- if (Flags.floatingMenuDragToHide()) {
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification);
- } else {
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
- }
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
+ mDragToInteractView = new DragToInteractView(context);
DismissViewUtils.setup(mDismissView);
+ mDismissView.getCircle().setId(R.id.action_remove_menu);
mNotificationFactory = new MenuNotificationFactory(context);
mNotificationManager = context.getSystemService(NotificationManager.class);
- mDragToInteractAnimationController = new DragToInteractAnimationController(
- mDismissView, mMenuView);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDragToInteractView, mMenuView);
+ } else {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, mMenuView);
+ }
mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ mDragToInteractAnimationController.animateInteractMenu(
+ target.getTargetView().getId(), /* scaleUp= */ true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velocityX, float velocityY, boolean wasFlungOut) {
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateInteractMenu(
+ target.getTargetView().getId(), /* scaleUp= */ false);
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (Flags.floatingMenuDragToHide()) {
- hideMenuAndShowNotification();
- } else {
- hideMenuAndShowMessage();
- }
- mDismissView.hide();
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ dispatchAccessibilityAction(target.getTargetView().getId());
}
});
@@ -260,7 +277,11 @@
});
addView(mMenuView, LayerIndex.MENU_VIEW);
- addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ if (Flags.floatingMenuDragToEdit()) {
+ addView(mDragToInteractView, LayerIndex.DISMISS_VIEW);
+ } else {
+ addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ }
addView(mMessageView, LayerIndex.MESSAGE_VIEW);
if (Flags.floatingMenuAnimatedTuck()) {
@@ -270,6 +291,7 @@
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mDragToInteractView.updateResources();
mDismissView.updateResources();
mDragToInteractAnimationController.updateResources();
}
@@ -426,6 +448,23 @@
}
}
+ void dispatchAccessibilityAction(int id) {
+ if (id == R.id.action_remove_menu) {
+ if (Flags.floatingMenuDragToHide()) {
+ hideMenuAndShowNotification();
+ } else {
+ hideMenuAndShowMessage();
+ }
+ } else if (id == R.id.action_edit
+ && Flags.floatingMenuDragToEdit()) {
+ mMenuView.gotoEditScreen();
+ }
+ mDismissView.hide();
+ mDragToInteractView.hide();
+ mDragToInteractAnimationController.animateInteractMenu(
+ id, /* scaleUp= */ false);
+ }
+
private CharSequence getMigrationMessage() {
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -473,7 +512,8 @@
mEduTooltipView = Optional.empty();
}
- private void hideMenuAndShowMessage() {
+ @VisibleForTesting
+ void hideMenuAndShowMessage() {
final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
SHOW_MESSAGE_DELAY_MS,
AccessibilityManager.FLAG_CONTENT_TEXT
@@ -483,7 +523,8 @@
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
}
- private void hideMenuAndShowNotification() {
+ @VisibleForTesting
+ void hideMenuAndShowNotification() {
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
showNotification();
}
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 1f54952..bc9d1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -39,7 +39,16 @@
MenuViewLayerController(Context context, WindowManager windowManager,
AccessibilityManager accessibilityManager, SecureSettings secureSettings) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this,
+
+ MenuViewModel menuViewModel = new MenuViewModel(
+ context, accessibilityManager, secureSettings);
+ MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager);
+
+ mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager,
+ menuViewModel,
+ menuViewAppearance,
+ new MenuView(context, menuViewModel, menuViewAppearance, secureSettings),
+ this,
secureSettings);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 9bcab57..9087816 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -16,10 +16,12 @@
package com.android.systemui.accessibility.floatingmenu;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -27,10 +29,12 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import org.junit.Before;
import org.junit.Rule;
@@ -46,6 +50,7 @@
@TestableLooper.RunWithLooper
public class DragToInteractAnimationControllerTest extends SysuiTestCase {
private DragToInteractAnimationController mDragToInteractAnimationController;
+ private DragToInteractView mInteractView;
private DismissView mDismissView;
@Rule
@@ -57,29 +62,72 @@
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ mockSecureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
- stubMenuViewAppearance);
+ final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
+ stubMenuViewAppearance, mockSecureSettings));
+ mInteractView = spy(new DragToInteractView(mContext));
mDismissView = spy(new DismissView(mContext));
- DismissViewUtils.setup(mDismissView);
- mDragToInteractAnimationController = new DragToInteractAnimationController(
- mDismissView, stubMenuView);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mInteractView, stubMenuView);
+ } else {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, stubMenuView);
+ }
+
+ mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+ }
+ });
}
@Test
- public void showDismissView_success() {
- mDragToInteractAnimationController.showDismissView(true);
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void showDismissView_success_old() {
+ mDragToInteractAnimationController.showInteractView(true);
verify(mDismissView).show();
}
@Test
- public void hideDismissView_success() {
- mDragToInteractAnimationController.showDismissView(false);
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void hideDismissView_success_old() {
+ mDragToInteractAnimationController.showInteractView(false);
verify(mDismissView).hide();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void showDismissView_success() {
+ mDragToInteractAnimationController.showInteractView(true);
+
+ verify(mInteractView).show();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void hideDismissView_success() {
+ mDragToInteractAnimationController.showInteractView(false);
+
+ verify(mInteractView).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 215f93d..e0df1e0 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
@@ -17,6 +17,7 @@
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.mock;
@@ -42,6 +43,7 @@
import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
@@ -79,10 +81,12 @@
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ secureSettings));
mViewPropertyAnimator = spy(mMenuView.animate());
doReturn(mViewPropertyAnimator).when(mMenuView).animate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 9c8de30..c2ed7d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -22,10 +22,13 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -37,7 +40,9 @@
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
@@ -49,6 +54,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/** Tests for {@link MenuItemAccessibilityDelegate}. */
@SmallTest
@TestableLooper.RunWithLooper
@@ -59,17 +66,16 @@
@Mock
private AccessibilityManager mAccessibilityManager;
- @Mock
- private SecureSettings mSecureSettings;
- @Mock
- private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
-
+ private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private RecyclerView mStubListView;
private MenuView mMenuView;
+ private MenuViewLayer mMenuViewLayer;
private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
private MenuAnimationController mMenuAnimationController;
private final Rect mDraggableBounds = new Rect(100, 200, 300, 400);
+ private final AtomicBoolean mEditReceived = new AtomicBoolean(false);
+
@Before
public void setUp() {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
@@ -80,20 +86,28 @@
final int halfScreenHeight =
stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ mSecureSettings));
mMenuView.setTranslationY(halfScreenHeight);
+ mMenuViewLayer = spy(new MenuViewLayer(
+ mContext, stubWindowManager, mAccessibilityManager,
+ stubMenuViewModel, stubMenuViewAppearance, mMenuView,
+ mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+
doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
mStubListView = new RecyclerView(mContext);
mMenuAnimationController = spy(new MenuAnimationController(mMenuView,
stubMenuViewAppearance));
mMenuItemAccessibilityDelegate =
new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate(
- mStubListView), mMenuAnimationController);
+ mStubListView), mMenuAnimationController, mMenuViewLayer);
+ mEditReceived.set(false);
}
@Test
- public void getAccessibilityActionList_matchSize() {
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void getAccessibilityActionList_matchSize_withoutEdit() {
final AccessibilityNodeInfoCompat info =
new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
@@ -103,6 +117,17 @@
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void getAccessibilityActionList_matchSize() {
+ final AccessibilityNodeInfoCompat info =
+ new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
+
+ mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
+
+ assertThat(info.getActionList().size()).isEqualTo(7);
+ }
+
+ @Test
public void performMoveTopLeftAction_matchPosition() {
final boolean moveTopLeftAction =
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
@@ -169,13 +194,22 @@
@Test
public void performRemoveMenuAction_success() {
- mMenuAnimationController.setDismissCallback(mStubDismissCallback);
final boolean removeMenuAction =
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
R.id.action_remove_menu, null);
assertThat(removeMenuAction).isTrue();
- verify(mMenuAnimationController).removeMenu();
+ verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_remove_menu);
+ }
+
+ @Test
+ public void performEditAction_success() {
+ final boolean editAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_edit, null);
+
+ assertThat(editAction).isTrue();
+ verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_edit);
}
@Test
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 e1522f5..9e8c6b3 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
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
import static android.view.View.OVER_SCROLL_NEVER;
import static com.google.common.truth.Truth.assertThat;
@@ -27,6 +28,8 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -38,10 +41,11 @@
import androidx.test.filters.SmallTest;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
import org.junit.After;
@@ -71,6 +75,7 @@
private DragToInteractAnimationController mDragToInteractAnimationController;
private RecyclerView mStubListView;
private DismissView mDismissView;
+ private DragToInteractView mInteractView;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -81,19 +86,28 @@
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
windowManager);
- mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+ mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ secureSettings);
mStubMenuView.setTranslationX(0);
mStubMenuView.setTranslationY(0);
mMenuAnimationController = spy(new MenuAnimationController(
mStubMenuView, stubMenuViewAppearance));
+ mInteractView = spy(new DragToInteractView(mContext));
mDismissView = spy(new DismissView(mContext));
- DismissViewUtils.setup(mDismissView);
- mDragToInteractAnimationController =
- spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+ mInteractView, mStubMenuView));
+ } else {
+ mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+ mDismissView, mStubMenuView));
+ }
+
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
mDragToInteractAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
@@ -115,7 +129,7 @@
@Test
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
- doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
+ doReturn(empty).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
@@ -136,6 +150,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void onActionMoveEvent_shouldShowDismissView() {
final int offset = 100;
final MotionEvent stubDownEvent =
@@ -154,6 +169,25 @@
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onActionMoveEvent_shouldShowInteractView() {
+ 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(mInteractView).show();
+ }
+
+ @Test
public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
final int offset = 100;
final MotionEvent stubDownEvent =
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 5e5273b..783a542 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
@@ -30,6 +30,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -72,6 +73,8 @@
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -81,6 +84,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
@@ -122,18 +126,17 @@
private SysuiTestableContext mSpyContext = getContext();
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
-
- @Mock
- private SecureSettings mSecureSettings;
-
@Mock
private WindowManager mStubWindowManager;
-
@Mock
private AccessibilityManager mStubAccessibilityManager;
+ private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+ private final ArgumentMatcher<IntentFilter> mNotificationMatcher =
+ (arg) -> arg.hasAction(ACTION_UNDO) && arg.hasAction(ACTION_DELETE);
+
@Before
public void setUp() throws Exception {
mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
@@ -145,8 +148,16 @@
new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
- mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager,
- mStubAccessibilityManager, mFloatingMenu, mSecureSettings);
+ MenuViewModel menuViewModel = new MenuViewModel(
+ mSpyContext, mStubAccessibilityManager, mSecureSettings);
+ MenuViewAppearance menuViewAppearance = new MenuViewAppearance(
+ mSpyContext, mStubWindowManager);
+ mMenuView = spy(
+ new MenuView(mSpyContext, menuViewModel, menuViewAppearance, mSecureSettings));
+
+ mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
+ mStubAccessibilityManager, menuViewModel, menuViewAppearance, mMenuView,
+ mFloatingMenu, mSecureSettings));
mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
@@ -187,7 +198,7 @@
}
@Test
- public void onAttachedToWindow_menuIsGone() {
+ public void onDetachedFromWindow_menuIsGone() {
mMenuViewLayer.onDetachedFromWindow();
final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
@@ -236,6 +247,27 @@
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onEditAction_gotoEditScreen_isCalled() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+ verify(mMenuView).gotoEditScreen();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onDismissAction_hideMenuAndShowNotification() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+ verify(mMenuViewLayer).hideMenuAndShowNotification();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onDismissAction_hideMenuAndShowMessage() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+ verify(mMenuViewLayer).hideMenuAndShowMessage();
+ }
+
+ @Test
public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
final float menuTop = STATUS_BAR_HEIGHT + 100;
mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
@@ -307,19 +339,13 @@
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() {
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mMockNotificationManager).notify(
eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN),
any(Notification.class));
- ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(
- IntentFilter.class);
verify(mSpyContext).registerReceiver(
- any(BroadcastReceiver.class),
- intentFilterCaptor.capture(),
- anyInt());
- assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue();
- assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue();
+ any(BroadcastReceiver.class), argThat(mNotificationMatcher), anyInt());
}
@Test
@@ -327,10 +353,10 @@
public void receiveActionUndo_dismissNotificationAndMenuVisible() {
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
BroadcastReceiver.class);
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
- any(IntentFilter.class), anyInt());
+ argThat(mNotificationMatcher), anyInt());
broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO));
verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -344,10 +370,10 @@
public void receiveActionDelete_dismissNotificationAndHideMenu() {
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
BroadcastReceiver.class);
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
- any(IntentFilter.class), anyInt());
+ argThat(mNotificationMatcher), anyInt());
broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE));
verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -423,10 +449,12 @@
});
}
- private void dragMenuThenReleasedInTarget() {
+ private void dragMenuThenReleasedInTarget(int id) {
MagnetizedObject.MagnetListener magnetListener =
- mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener();
+ mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(id);
+ View view = mock(View.class);
+ when(view.getId()).thenReturn(id);
magnetListener.onReleasedInTarget(
- new MagnetizedObject.MagneticTarget(mock(View.class), 200));
+ new MagnetizedObject.MagneticTarget(view, 200));
}
}
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 8da6cf9..7c97f53 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
@@ -17,15 +17,19 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.app.UiModeManager.MODE_NIGHT_YES;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
+
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.UiModeManager;
+import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -36,6 +40,8 @@
import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
@@ -65,17 +71,23 @@
@Mock
private AccessibilityManager mAccessibilityManager;
+ private SysuiTestableContext mSpyContext;
+
@Before
public void setUp() throws Exception {
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+ mSpyContext = spy(mContext);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
- mLastPosition = Prefs.getString(mContext,
+ mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
+ mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
+ secureSettings));
+ mLastPosition = Prefs.getString(mSpyContext,
Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
}
@@ -154,6 +166,25 @@
assertThat(radiiAnimator.isStarted()).isTrue();
}
+ @Test
+ public void getIntentForEditScreen_validate() {
+ Intent intent = mMenuView.getIntentForEditScreen();
+ String[] targets = intent.getBundleExtra(
+ ":settings:show_fragment_args").getStringArray("targets");
+
+ assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void gotoEditScreen_sendsIntent() {
+ // Notably, this shouldn't crash the settings app,
+ // because the button target args are configured.
+ mMenuView.gotoEditScreen();
+ verify(mSpyContext).startActivity(any());
+ }
+
private InstantInsetLayerDrawable getMenuViewInsetLayer() {
return (InstantInsetLayerDrawable) mMenuView.getBackground();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
index 10c8caa..8399fa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
@@ -16,11 +16,27 @@
package com.android.systemui.accessibility.utils;
-import android.os.SystemClock;
+import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.Set;
+import java.util.StringJoiner;
import java.util.function.BooleanSupplier;
public class TestUtils {
+ private static final ComponentName TEST_COMPONENT_A = new ComponentName("pkg", "A");
+ private static final ComponentName TEST_COMPONENT_B = new ComponentName("pkg", "B");
+ public static final String[] TEST_BUTTON_TARGETS = {
+ TEST_COMPONENT_A.flattenToString(), TEST_COMPONENT_B.flattenToString()};
public static long DEFAULT_CONDITION_DURATION = 5_000;
/**
@@ -55,4 +71,28 @@
SystemClock.sleep(sleepMs);
}
}
+
+ /**
+ * Returns a mock secure settings configured to return information needed for tests.
+ * Currently, this only includes button targets.
+ */
+ public static SecureSettings mockSecureSettings() {
+ SecureSettings secureSettings = mock(SecureSettings.class);
+
+ final String targets = getShortcutTargets(
+ Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B));
+ when(secureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).thenReturn(targets);
+
+ return secureSettings;
+ }
+
+ private static String getShortcutTargets(Set<ComponentName> components) {
+ final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+ for (ComponentName target : components) {
+ stringJoiner.add(target.flattenToString());
+ }
+ return stringJoiner.toString();
+ }
}