Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6431d724 authored by Riley Jones's avatar Riley Jones Committed by Android (Google) Code Review
Browse files

Merge "Create a DragToInteractView derived from DismissView, which can feature...

Merge "Create a DragToInteractView derived from DismissView, which can feature multiple views to drag to." into main
parents f1672dcc b6e63e28
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -443,6 +443,18 @@ public final class Settings {
    public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS =
            "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
    /**
     * Activity Action: Show settings to allow configuration of an accessibility
     * shortcut belonging to an accessibility feature or features.
     * <p>
     * Input: ":settings:show_fragment_args" must contain "targets" denoting the services to edit.
     * <p>
     * Output: Nothing.
     * @hide
     **/
    public static final String ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS =
            "android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS";
    /**
     * Activity Action: Show settings to allow configuration of accessibility color and motion.
     * <p>
+7 −0
Original line number Diff line number Diff line
@@ -16,6 +16,13 @@ flag {
    bug: "298718415"
}

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"
+1 −0
Original line number Diff line number Diff line
@@ -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"/>
+2 −0
Original line number Diff line number Diff line
@@ -2560,6 +2560,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] -->
+153 −80
Original line number Diff line number Diff line
@@ -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(DismissView dismissView, MenuView menuView) {
        mDismissView = dismissView;
        mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
        mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
    DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) {
        mDismissView = null;
        mInteractView = interactView;
        mInteractView.setPivotX(interactView.getWidth() / 2.0f);
        mInteractView.setPivotY(interactView.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));
        mInteractMap = new ArrayMap<>();
        interactView.getInteractMap().forEach((viewId, pair) -> {
            DismissCircleView circleView = pair.getFirst();
            createMagnetizedObjectAndAnimator(circleView);
        });

        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);
                    }
    DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
        mDismissView = dismissView;
        mInteractView = null;
        mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
        mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
        mMenuView = menuView;

                    @Override
                    public float getHeight(MenuView underlyingObject) {
                        return underlyingObject.getHeight();
                    }
        updateResources();

                    @Override
                    public float getWidth(MenuView underlyingObject) {
                        return underlyingObject.getWidth();
        mInteractMap = new ArrayMap<>();
        createMagnetizedObjectAndAnimator(dismissView.getCircle());
    }
                };

        final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
                dismissView.getCircle(), (int) mMinDismissSize);
        mMagnetizedObject.addTarget(magneticTarget);
        mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU);
    void showInteractView(boolean show) {
        if (Flags.floatingMenuDragToEdit() && mInteractView != null) {
            if (show) {
                mInteractView.show();
            } else {
                mInteractView.hide();
            }

    void showDismissView(boolean show) {
        } 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 @@ class DragToInteractAnimationController {
     * 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;
    }

    /**
     * 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);
                }
            }
        });

    interface DismissCallback {
        void onDismiss();
        mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator));
    }
}
Loading