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

Commit ae8863ee authored by Peter Liang's avatar Peter Liang
Browse files

Support the feature of Drag to remove for the Accessibility Floating Menu(1/n).

Goals:
Based on the animation implementation and improvements, provide the capability of dismissing the accessibility floating menu view on the home screen.

Actions:
1) Add and interact between the magnetized object and magnetic target.

Bug: 237716018
Test: atest MenuTouchHandlerTest MenuViewLayerTest
Change-Id: I9428c3a3e4e26b9fa4595bc2b6a3a7526c54cef1
parent f9b8309c
Loading
Loading
Loading
Loading
+175 −0
Original line number Diff line number Diff line
/*
 * 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<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);
    }

    @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;
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ 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 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 +299,15 @@ class MenuAnimationController {
        mMenuView.onDraggingStart();
    }

    void startShrinkAnimation(Runnable endAction) {
        mMenuView.animate()
                .scaleX(SCALE_SHRINK)
                .scaleY(SCALE_SHRINK)
                .alpha(COMPLETELY_TRANSPARENT)
                .translationY(mMenuView.getTranslationY())
                .withEndAction(endAction).start();
    }

    private void onSpringAnimationEnd(PointF position) {
        mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
        constrainPositionAndUpdate(position);
+21 −4
Original line number Diff line number Diff line
@@ -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();
                    }

                    mDismissAnimationController.showDismissView(/* show= */ true);

                    if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
                        mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
                    mMenuAnimationController.moveToPositionYIfNeeded(mMenuTranslationDown.y + dy);
                        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.
+5 −2
Original line number Diff line number Diff line
@@ -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
@@ -70,7 +70,6 @@ class MenuView extends FrameLayout implements
        mMenuViewModel = menuViewModel;
        mMenuViewAppearance = menuViewAppearance;
        mMenuAnimationController = new MenuAnimationController(this);

        mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
        mTargetFeaturesView = new RecyclerView(context);
        mTargetFeaturesView.setAdapter(mAdapter);
@@ -112,6 +111,10 @@ class MenuView extends FrameLayout implements
        mTargetFeaturesView.addOnItemTouchListener(listener);
    }

    MenuAnimationController getMenuAnimationController() {
        return mMenuAnimationController;
    }

    @SuppressLint("NotifyDataSetChanged")
    private void onItemSizeChanged() {
        mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
+72 −2
Original line number Diff line number Diff line
@@ -19,12 +19,20 @@ package com.android.systemui.accessibility.floatingmenu;
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.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;
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;

@@ -34,24 +42,83 @@ import java.lang.annotation.RetentionPolicy;
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout {
    private final MenuView mMenuView;
    private final DismissView mDismissView;
    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
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface LayerIndex {
        int MENU_VIEW = 0;
        int DISMISS_VIEW = 1;
    }

    @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) {
    MenuViewLayer(@NonNull Context context, WindowManager windowManager,
            IAccessibilityFloatingMenu floatingMenu) {
        super(context);

        mFloatingMenu = floatingMenu;

        final MenuViewModel menuViewModel = new MenuViewModel(context);
        final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
                windowManager);
        mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
        final MenuAnimationController menuAnimationController =
                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) {
                menuAnimationController.startShrinkAnimation(() -> {
                    mMenuView.hide();

                    mHandler.post(mDismissMenuAction);
                });

                mDismissView.hide();
                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
            }
        });

        final MenuListViewTouchHandler menuListViewTouchHandler = new MenuListViewTouchHandler(
                menuAnimationController, mDismissAnimationController);
        mMenuView.addOnItemTouchListenerToList(menuListViewTouchHandler);

        addView(mMenuView, LayerIndex.MENU_VIEW);
        addView(mDismissView, LayerIndex.DISMISS_VIEW);
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDismissView.updateResources();
    }

    @Override
@@ -68,6 +135,7 @@ class MenuViewLayer extends FrameLayout {
        super.onAttachedToWindow();

        mMenuView.show();
        mContext.registerComponentCallbacks(mDismissAnimationController);
    }

    @Override
@@ -75,5 +143,7 @@ class MenuViewLayer extends FrameLayout {
        super.onDetachedFromWindow();

        mMenuView.hide();
        mHandler.removeCallbacksAndMessages(/* token= */ null);
        mContext.unregisterComponentCallbacks(mDismissAnimationController);
    }
}
Loading