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

Commit 47884428 authored by Tyler Freeman's avatar Tyler Freeman Committed by Android (Google) Code Review
Browse files

Merge changes I01ea9c44,I57014e31,Ic3727889,I1ae12d1a into main

* changes:
  fix(magnification fling): add stop fling test to controller test
  fix(magnification fling): stop fling animation if they put their finger down
  feat(magnification fling): add fling momentum velocity tracking to panning gesture
  fix(fullscreen magnification): fix test sending incorrect TouchEvents
parents 95dd2538 cfc401cf
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view.accessibility;

import android.view.MagnificationSpec;

/**
 * A callback for magnification animation result.
 * @hide
@@ -31,4 +33,16 @@ public interface MagnificationAnimationCallback {
     *                change. Otherwise {@code false}
     */
    void onResult(boolean success);

    /**
     * Called when the animation is finished or interrupted during animating.
     *
     * @param success {@code true} if animating successfully with given spec or the spec did not
     *                change. Otherwise {@code false}
     * @param lastSpecSent the last spec that was sent to WindowManager for animation, in case you
     *                     need to update the local copy
     */
    default void onResult(boolean success, MagnificationSpec lastSpecSent) {
        onResult(success);
    }
}
 No newline at end of file
+7 −0
Original line number Diff line number Diff line
@@ -51,6 +51,13 @@ flag {
    bug: "300002193"
}

flag {
    name: "fullscreen_fling_gesture"
    namespace: "accessibility"
    description: "When true, adds a fling gesture animation for fullscreen magnification"
    bug: "319175022"
}

flag {
    name: "pinch_zoom_zero_min_span"
    namespace: "accessibility"
+282 −10
Original line number Diff line number Diff line
@@ -25,7 +25,9 @@ import static com.android.server.accessibility.AccessibilityManagerService.INVAL

import android.accessibilityservice.MagnificationConfig;
import android.animation.Animator;
import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -51,6 +53,7 @@ import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.MagnificationAnimationCallback;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;

import com.android.internal.R;
import com.android.internal.accessibility.common.MagnificationConstants;
@@ -60,6 +63,7 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.Flags;
import com.android.server.wm.WindowManagerInternal;

import java.util.ArrayList;
@@ -86,6 +90,8 @@ public class FullScreenMagnificationController implements
    private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;

    private final Object mLock;
    private final Supplier<Scroller> mScrollerSupplier;
    private final Supplier<TimeAnimator> mTimeAnimatorSupplier;

    private final ControllerContext mControllerCtx;

@@ -149,7 +155,13 @@ public class FullScreenMagnificationController implements

        DisplayMagnification(int displayId) {
            mDisplayId = displayId;
            mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
            mSpecAnimationBridge =
                    new SpecAnimationBridge(
                            mControllerCtx,
                            mLock,
                            mDisplayId,
                            mScrollerSupplier,
                            mTimeAnimatorSupplier);
        }

        /**
@@ -406,6 +418,52 @@ public class FullScreenMagnificationController implements
            }
        }

        void startFlingAnimation(
                float xPixelsPerSecond,
                float yPixelsPerSecond,
                MagnificationAnimationCallback animationCallback
        ) {
            if (DEBUG) {
                Slog.i(LOG_TAG,
                        "startFlingAnimation(spec = " + xPixelsPerSecond + ", animationCallback = "
                                + animationCallback + ")");
            }
            if (Thread.currentThread().getId() == mMainThreadId) {
                mSpecAnimationBridge.startFlingAnimation(
                        xPixelsPerSecond,
                        yPixelsPerSecond,
                        getMinOffsetXLocked(),
                        getMaxOffsetXLocked(),
                        getMinOffsetYLocked(),
                        getMaxOffsetYLocked(),
                        animationCallback);
            } else {
                final Message m =
                        PooledLambda.obtainMessage(
                                SpecAnimationBridge::startFlingAnimation,
                                mSpecAnimationBridge,
                                xPixelsPerSecond,
                                yPixelsPerSecond,
                                getMinOffsetXLocked(),
                                getMaxOffsetXLocked(),
                                getMinOffsetYLocked(),
                                getMaxOffsetYLocked(),
                                animationCallback);
                mControllerCtx.getHandler().sendMessage(m);
            }
        }

        void cancelFlingAnimation() {
            if (DEBUG) {
                Slog.i(LOG_TAG, "cancelFlingAnimation()");
            }
            if (Thread.currentThread().getId() == mMainThreadId) {
                mSpecAnimationBridge.cancelFlingAnimation();
            } else {
                mControllerCtx.getHandler().post(mSpecAnimationBridge::cancelFlingAnimation);
            }
        }

        /**
         * Get the ID of the last service that changed the magnification spec.
         *
@@ -759,6 +817,63 @@ public class FullScreenMagnificationController implements
            sendSpecToAnimation(mCurrentMagnificationSpec, null);
        }

        @GuardedBy("mLock")
        void startFling(float xPixelsPerSecond, float yPixelsPerSecond, int id) {
            if (!mRegistered) {
                return;
            }
            if (!isActivated()) {
                return;
            }

            if (id != INVALID_SERVICE_ID) {
                mIdOfLastServiceToMagnify = id;
            }

            startFlingAnimation(
                    xPixelsPerSecond,
                    yPixelsPerSecond,
                    new MagnificationAnimationCallback() {
                        @Override
                        public void onResult(boolean success) {
                            // never called
                        }

                        @Override
                        public void onResult(boolean success, MagnificationSpec lastSpecSent) {
                            if (DEBUG) {
                                Slog.i(
                                        LOG_TAG,
                                        "startFlingAnimation finished( "
                                                + success
                                                + " = "
                                                + lastSpecSent.offsetX
                                                + ", "
                                                + lastSpecSent.offsetY
                                                + ")");
                            }
                            synchronized (mLock) {
                                mCurrentMagnificationSpec.setTo(lastSpecSent);
                                onMagnificationChangedLocked();
                            }
                        }
                    });
        }


        @GuardedBy("mLock")
        void cancelFling(int id) {
            if (!mRegistered) {
                return;
            }

            if (id != INVALID_SERVICE_ID) {
                mIdOfLastServiceToMagnify = id;
            }

            cancelFlingAnimation();
        }

        boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
            if (DEBUG) {
                Slog.i(LOG_TAG,
@@ -838,7 +953,9 @@ public class FullScreenMagnificationController implements
                magnificationInfoChangedCallback,
                scaleProvider,
                /* thumbnailSupplier= */ null,
                backgroundExecutor);
                backgroundExecutor,
                () -> new Scroller(context),
                TimeAnimator::new);
    }

    /** Constructor for tests */
@@ -849,9 +966,13 @@ public class FullScreenMagnificationController implements
            @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
            @NonNull MagnificationScaleProvider scaleProvider,
            Supplier<MagnificationThumbnail> thumbnailSupplier,
            @NonNull Executor backgroundExecutor) {
            @NonNull Executor backgroundExecutor,
            Supplier<Scroller> scrollerSupplier,
            Supplier<TimeAnimator> timeAnimatorSupplier) {
        mControllerCtx = ctx;
        mLock = lock;
        mScrollerSupplier = scrollerSupplier;
        mTimeAnimatorSupplier = timeAnimatorSupplier;
        mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
        mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
        addInfoChangedCallback(magnificationInfoChangedCallback);
@@ -1436,6 +1557,42 @@ public class FullScreenMagnificationController implements
        }
    }

    /**
     * Call after a pan ends, if the velocity has passed the threshold, to start a fling animation.
     *
     * @param displayId The logical display id.
     * @param xPixelsPerSecond the velocity of the last pan gesture in the X direction, in current
     *     screen pixels per second.
     * @param yPixelsPerSecond the velocity of the last pan gesture in the Y direction, in current
     *     screen pixels per second.
     * @param id the ID of the service requesting the change
     */
    public void startFling(int displayId, float xPixelsPerSecond, float yPixelsPerSecond, int id) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return;
            }
            display.startFling(xPixelsPerSecond, yPixelsPerSecond, id);
        }
    }

    /**
     * Call to cancel the fling animation if it is running. Call this on any ACTION_DOWN event.
     *
     * @param displayId The logical display id.
     * @param id the ID of the service requesting the change
     */
    public void cancelFling(int displayId, int id) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return;
            }
            display.cancelFling(id);
        }
    }

    /**
     * Get the ID of the last service that changed the magnification spec.
     *
@@ -1698,7 +1855,15 @@ public class FullScreenMagnificationController implements
        @GuardedBy("mLock")
        private boolean mEnabled = false;

        private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
        private final Scroller mScroller;
        private final TimeAnimator mScrollAnimator;

        private SpecAnimationBridge(
                ControllerContext ctx,
                Object lock,
                int displayId,
                Supplier<Scroller> scrollerSupplier,
                Supplier<TimeAnimator> timeAnimatorSupplier) {
            mControllerCtx = ctx;
            mLock = lock;
            mDisplayId = displayId;
@@ -1709,6 +1874,37 @@ public class FullScreenMagnificationController implements
            mValueAnimator.setFloatValues(0.0f, 1.0f);
            mValueAnimator.addUpdateListener(this);
            mValueAnimator.addListener(this);

            if (Flags.fullscreenFlingGesture()) {
                mScroller = scrollerSupplier.get();
                mScrollAnimator = timeAnimatorSupplier.get();
                mScrollAnimator.addListener(this);
                mScrollAnimator.setTimeListener(
                        (animation, totalTime, deltaTime) -> {
                            synchronized (mLock) {
                                if (DEBUG) {
                                    Slog.v(
                                            LOG_TAG,
                                            "onScrollAnimationUpdate: "
                                                    + mEnabled + " : " + totalTime);
                                }

                                if (mEnabled) {
                                    if (!mScroller.computeScrollOffset()) {
                                        animation.end();
                                        return;
                                    }

                                    mEndMagnificationSpec.offsetX = mScroller.getCurrX();
                                    mEndMagnificationSpec.offsetY = mScroller.getCurrY();
                                    setMagnificationSpecLocked(mEndMagnificationSpec);
                                }
                            }
                        });
            } else {
                mScroller = null;
                mScrollAnimator = null;
            }
        }

        /**
@@ -1735,16 +1931,20 @@ public class FullScreenMagnificationController implements
            }
        }

        void updateSentSpecMainThread(MagnificationSpec spec,
                MagnificationAnimationCallback animationCallback) {
            if (mValueAnimator.isRunning()) {
                mValueAnimator.cancel();
            }
        @MainThread
        void updateSentSpecMainThread(
                MagnificationSpec spec, MagnificationAnimationCallback animationCallback) {
            cancelAnimations();

            mAnimationCallback = animationCallback;
            // If the current and sent specs don't match, update the sent spec.
            synchronized (mLock) {
                final boolean changed = !mSentMagnificationSpec.equals(spec);
                if (DEBUG_SET_MAGNIFICATION_SPEC) {
                    Slog.d(
                            LOG_TAG,
                            "updateSentSpecMainThread: " + mEnabled + " : changed: " + changed);
                }
                if (changed) {
                    if (mAnimationCallback != null) {
                        animateMagnificationSpecLocked(spec);
@@ -1757,12 +1957,13 @@ public class FullScreenMagnificationController implements
            }
        }

        @MainThread
        private void sendEndCallbackMainThread(boolean success) {
            if (mAnimationCallback != null) {
                if (DEBUG) {
                    Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success);
                }
                mAnimationCallback.onResult(success);
                mAnimationCallback.onResult(success, mSentMagnificationSpec);
                mAnimationCallback = null;
            }
        }
@@ -1830,6 +2031,77 @@ public class FullScreenMagnificationController implements
        public void onAnimationRepeat(Animator animation) {

        }

        /**
         * Call after a pan ends, if the velocity has passed the threshold, to start a fling
         * animation.
         */
        @MainThread
        public void startFlingAnimation(
                float xPixelsPerSecond,
                float yPixelsPerSecond,
                float minX,
                float maxX,
                float minY,
                float maxY,
                MagnificationAnimationCallback animationCallback
        ) {
            if (!Flags.fullscreenFlingGesture()) {
                return;
            }
            cancelAnimations();

            mAnimationCallback = animationCallback;

            // We use this as a temp object to send updates every animation frame, so make sure it
            // matches the current spec before we start.
            mEndMagnificationSpec.setTo(mSentMagnificationSpec);

            if (DEBUG) {
                Slog.d(LOG_TAG, "startFlingAnimation: "
                        + "offsetX " + mSentMagnificationSpec.offsetX
                        + "offsetY " + mSentMagnificationSpec.offsetY
                        + "xPixelsPerSecond " + xPixelsPerSecond
                        + "yPixelsPerSecond " + yPixelsPerSecond
                        + "minX " + minX
                        + "maxX " + maxX
                        + "minY " + minY
                        + "maxY " + maxY
                );
            }

            mScroller.fling(
                    (int) mSentMagnificationSpec.offsetX,
                    (int) mSentMagnificationSpec.offsetY,
                    (int) xPixelsPerSecond,
                    (int) yPixelsPerSecond,
                    (int) minX,
                    (int) maxX,
                    (int) minY,
                    (int) maxY);

            mScrollAnimator.start();
        }

        @MainThread
        void cancelAnimations() {
            if (mValueAnimator.isRunning()) {
                mValueAnimator.cancel();
            }

            cancelFlingAnimation();
        }

        @MainThread
        void cancelFlingAnimation() {
            if (!Flags.fullscreenFlingGesture()) {
                return;
            }
            if (mScrollAnimator.isRunning()) {
                mScrollAnimator.cancel();
            }
            mScroller.forceFinished(true);
        }
    }

    private static class ScreenStateObserver extends BroadcastReceiver {
+107 −8
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;

import com.android.internal.R;
@@ -174,6 +175,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

    private final boolean mIsWatch;

    @Nullable private VelocityTracker mVelocityTracker;
    private final int mMinimumVelocity;
    private final int mMaximumVelocity;

    public FullScreenMagnificationGestureHandler(@UiContext Context context,
            FullScreenMagnificationController fullScreenMagnificationController,
            AccessibilityTraceManager trace,
@@ -184,15 +189,25 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            @NonNull WindowMagnificationPromptController promptController,
            int displayId,
            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
        this(context, fullScreenMagnificationController, trace, callback,
                detectSingleFingerTripleTap, detectTwoFingerTripleTap,
                detectShortcutTrigger, promptController, displayId,
                fullScreenMagnificationVibrationHelper, /* magnificationLogger= */ null);
        this(
                context,
                fullScreenMagnificationController,
                trace,
                callback,
                detectSingleFingerTripleTap,
                detectTwoFingerTripleTap,
                detectShortcutTrigger,
                promptController,
                displayId,
                fullScreenMagnificationVibrationHelper,
                /* magnificationLogger= */ null,
                ViewConfiguration.get(context));
    }

    /** Constructor for tests. */
    @VisibleForTesting
    FullScreenMagnificationGestureHandler(@UiContext Context context,
    FullScreenMagnificationGestureHandler(
            @UiContext Context context,
            FullScreenMagnificationController fullScreenMagnificationController,
            AccessibilityTraceManager trace,
            Callback callback,
@@ -202,7 +217,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            @NonNull WindowMagnificationPromptController promptController,
            int displayId,
            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
            MagnificationLogger magnificationLogger) {
            MagnificationLogger magnificationLogger,
            ViewConfiguration viewConfiguration) {
        super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
                detectShortcutTrigger, trace, callback);
        if (DEBUG_ALL) {
@@ -212,6 +228,15 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                            + ", detectTwoFingerTripleTap = " + detectTwoFingerTripleTap
                            + ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
        }

        if (Flags.fullscreenFlingGesture()) {
            mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
            mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
        } else {
            mMinimumVelocity = 0;
            mMaximumVelocity = 0;
        }

        mFullScreenMagnificationController = fullScreenMagnificationController;
        mMagnificationInfoChangedCallback =
                new FullScreenMagnificationController.MagnificationInfoChangedCallback() {
@@ -299,6 +324,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

    @Override
    void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (event.getActionMasked() == ACTION_DOWN) {
            cancelFling();
        }

        handleEventWith(mCurrentState, event, rawEvent, policyFlags);
    }

@@ -501,6 +530,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                }
                persistScaleAndTransitionTo(mViewportDraggingState);
            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
                onPanningFinished(event);
                // if feature flag is enabled, currently only true on watches
                if (mIsSinglePanningEnabled) {
                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
@@ -578,6 +608,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                Slog.i(mLogTag, "Panned content by scrollX: " + distanceX
                        + " scrollY: " + distanceY);
            }
            onPan(second);
            mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
                    distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
            if (mIsSinglePanningEnabled) {
@@ -973,7 +1004,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                                    && overscrollState(event, mFirstPointerDownLocation)
                                    == OVERSCROLL_VERTICAL_EDGE) {
                                transitionToDelegatingStateAndClear();
                            }
                            } // TODO(b/319537921): should there be an else here?
                            //Primary pointer is swiping, so transit to PanningScalingState
                            transitToPanningScalingStateAndClear();
                        } else if (mIsSinglePanningEnabled
@@ -982,7 +1013,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                            if (overscrollState(event, mFirstPointerDownLocation)
                                    == OVERSCROLL_VERTICAL_EDGE) {
                                transitionToDelegatingStateAndClear();
                            }
                            } // TODO(b/319537921): should there be an else here?
                            transitToSinglePanningStateAndClear();
                        } else if (!mIsTwoFingerCountReached) {
                            // If it is a two-finger gesture, do not transition to the
@@ -1742,6 +1773,71 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
        }
    }

    /** Call during MOVE events for a panning gesture. */
    private void onPan(MotionEvent event) {
        if (!Flags.fullscreenFlingGesture()) {
            return;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    /**
     * Call during UP events for a panning gesture, so we can detect a fling and play a physics-
     * based fling animation.
     */
    private void onPanningFinished(MotionEvent event) {
        if (!Flags.fullscreenFlingGesture()) {
            return;
        }

        if (mVelocityTracker == null) {
            Log.e(mLogTag, "onPanningFinished: mVelocityTracker is null");
            return;
        }
        mVelocityTracker.addMovement(event);
        mVelocityTracker.computeCurrentVelocity(/* units= */ 1000, mMaximumVelocity);

        float xPixelsPerSecond = mVelocityTracker.getXVelocity();
        float yPixelsPerSecond = mVelocityTracker.getYVelocity();

        mVelocityTracker.recycle();
        mVelocityTracker = null;

        if (DEBUG_PANNING_SCALING) {
            Slog.v(
                    mLogTag,
                    "onPanningFinished: pixelsPerSecond: "
                            + xPixelsPerSecond
                            + ", "
                            + yPixelsPerSecond
                            + " mMinimumVelocity: "
                            + mMinimumVelocity);
        }

        if ((Math.abs(yPixelsPerSecond) > mMinimumVelocity)
                || (Math.abs(xPixelsPerSecond) > mMinimumVelocity)) {
            mFullScreenMagnificationController.startFling(
                    mDisplayId,
                    xPixelsPerSecond,
                    yPixelsPerSecond,
                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
        }
    }

    private void cancelFling() {
        if (!Flags.fullscreenFlingGesture()) {
            return;
        }

        mFullScreenMagnificationController.cancelFling(
                    mDisplayId,
                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
    }

    final class SinglePanningState extends SimpleOnGestureListener implements State {


@@ -1756,6 +1852,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            int action = event.getActionMasked();
            switch (action) {
                case ACTION_UP:
                    onPanningFinished(event);
                    // fall-through!
                case ACTION_CANCEL:
                    mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
                    mOverscrollHandler.clearEdgeState();
@@ -1770,6 +1868,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            if (mCurrentState != mSinglePanningState) {
                return true;
            }
            onPan(second);
            mFullScreenMagnificationController.offsetMagnifiedRegion(
                    mDisplayId,
                    distanceX,
+104 −7

File changed.

Preview size limit exceeded, changes collapsed.

Loading