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

Commit 9ec06e12 authored by ryanlwlin's avatar ryanlwlin
Browse files

Add callback for transition animation

To support transition animation while switching magnification mode,
We add the callback to know when the animation is finished.

Bug: 161669184
Test: FullScreenMagnificationControllerTest
Change-Id: I002e0a818a38d5928d98741284c68f6236745b6f
parent 933072bd
Loading
Loading
Loading
Loading
+106 −17
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.server.accessibility.magnification;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -61,6 +63,8 @@ public class FullScreenMagnificationController {
    private static final boolean DEBUG = false;
    private static final String LOG_TAG = "FullScreenMagnificationController";

    private static final Runnable STUB_RUNNABLE = () -> {
    };
    public static final float MIN_SCALE = 1.0f;
    public static final float MAX_SCALE = 8.0f;

@@ -292,7 +296,7 @@ public class FullScreenMagnificationController {
                    // Adjust the current spec's offsets if necessary.
                    if (updateCurrentSpecWithOffsetsLocked(
                            mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
                        sendSpecToAnimation(mCurrentMagnificationSpec, false);
                        sendSpecToAnimation(mCurrentMagnificationSpec, null);
                    }
                    onMagnificationChangedLocked();
                }
@@ -300,17 +304,18 @@ public class FullScreenMagnificationController {
            }
        }

        void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
        void sendSpecToAnimation(MagnificationSpec spec, Runnable endCallback) {
            if (DEBUG) {
                Slog.i(LOG_TAG,
                        "sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")");
                        "sendSpecToAnimation(spec = " + spec + ", endCallback = " + endCallback
                                + ")");
            }
            if (Thread.currentThread().getId() == mMainThreadId) {
                mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
                mSpecAnimationBridge.updateSentSpecMainThread(spec, endCallback);
            } else {
                final Message m = PooledLambda.obtainMessage(
                        SpecAnimationBridge::updateSentSpecMainThread,
                        mSpecAnimationBridge, spec, animate);
                        mSpecAnimationBridge, spec, endCallback);
                mControllerCtx.getHandler().sendMessage(m);
            }
        }
@@ -410,6 +415,11 @@ public class FullScreenMagnificationController {

        @GuardedBy("mLock")
        boolean reset(boolean animate) {
            return reset(transformToStubRunnable(animate));
        }

        @GuardedBy("mLock")
        boolean reset(Runnable endCallback) {
            if (!mRegistered) {
                return false;
            }
@@ -420,7 +430,7 @@ public class FullScreenMagnificationController {
                onMagnificationChangedLocked();
            }
            mIdOfLastServiceToMagnify = INVALID_ID;
            sendSpecToAnimation(spec, animate);
            sendSpecToAnimation(spec, endCallback);
            return changed;
        }

@@ -448,24 +458,24 @@ public class FullScreenMagnificationController {
            final float centerX = normPivotX + offsetX;
            final float centerY = normPivotY + offsetY;
            mIdOfLastServiceToMagnify = id;
            return setScaleAndCenter(scale, centerX, centerY, animate, id);
            return setScaleAndCenter(scale, centerX, centerY, transformToStubRunnable(animate), id);
        }

        @GuardedBy("mLock")
        boolean setScaleAndCenter(float scale, float centerX, float centerY,
                boolean animate, int id) {
                Runnable endCallback, int id) {
            if (!mRegistered) {
                return false;
            }
            if (DEBUG) {
                Slog.i(LOG_TAG,
                        "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
                                + ", centerY = " + centerY + ", animate = " + animate
                                + ", centerY = " + centerY + ", endCallback = " + endCallback
                                + ", id = " + id
                                + ")");
            }
            final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
            sendSpecToAnimation(mCurrentMagnificationSpec, animate);
            sendSpecToAnimation(mCurrentMagnificationSpec, endCallback);
            if (isMagnifying() && (id != INVALID_ID)) {
                mIdOfLastServiceToMagnify = id;
            }
@@ -531,7 +541,7 @@ public class FullScreenMagnificationController {
            if (id != INVALID_ID) {
                mIdOfLastServiceToMagnify = id;
            }
            sendSpecToAnimation(mCurrentMagnificationSpec, false);
            sendSpecToAnimation(mCurrentMagnificationSpec, null);
        }

        boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
@@ -865,12 +875,26 @@ public class FullScreenMagnificationController {
     *         the spec did not change
     */
    public boolean reset(int displayId, boolean animate) {
        return reset(displayId, animate ?  STUB_RUNNABLE : null);
    }

    /**
     * Resets the magnification scale and center, optionally animating the
     * transition.
     *
     * @param displayId The logical display id.
     * @param endCallback Called when the animation is ended or the spec did not change.
     *                    {@code null} to transition immediately
     * @return {@code true} if the magnification spec changed, {@code false} if
     *         the spec did not change
     */
    public boolean reset(int displayId, Runnable endCallback) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return false;
            }
            return display.reset(animate);
            return display.reset(endCallback);
        }
    }

@@ -921,7 +945,8 @@ public class FullScreenMagnificationController {
            if (display == null) {
                return false;
            }
            return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
            return display.setScaleAndCenter(Float.NaN, centerX, centerY,
                    animate ?  STUB_RUNNABLE : null, id);
        }
    }

@@ -944,12 +969,35 @@ public class FullScreenMagnificationController {
     */
    public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
            boolean animate, int id) {
        return setScaleAndCenter(displayId, scale, centerX, centerY,
                transformToStubRunnable(animate), id);
    }

    /**
     * Sets the scale and center of the magnified region, optionally
     * animating the transition. If animation is disabled, the transition
     * is immediate.
     *
     * @param displayId The logical display id.
     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
     * @param centerX the screen-relative X coordinate around which to
     *                center and scale, or {@link Float#NaN} to leave unchanged
     * @param centerY the screen-relative Y coordinate around which to
     *                center and scale, or {@link Float#NaN} to leave unchanged
     * @param endCallback called when the transition is finished successfully or the spec did not
     *                   change. {@code null} to transition immediately.
     * @param id the ID of the service requesting the change
     * @return {@code true} if the magnification spec changed, {@code false} if
     *         the spec did not change
     */
    public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
            Runnable endCallback, int id) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return false;
            }
            return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
            return display.setScaleAndCenter(scale, centerX, centerY, endCallback, id);
        }
    }

@@ -1160,7 +1208,8 @@ public class FullScreenMagnificationController {
     * Class responsible for animating spec on the main thread and sending spec
     * updates to the window manager.
     */
    private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
    private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener,
            Animator.AnimatorListener {
        private final ControllerContext mControllerCtx;

        /**
@@ -1180,6 +1229,8 @@ public class FullScreenMagnificationController {
         */
        private final ValueAnimator mValueAnimator;

        // Called when the callee wants animating and the sent spec matches the target spec.
        private Runnable mEndCallback;
        private final Object mLock;

        private final int mDisplayId;
@@ -1197,6 +1248,7 @@ public class FullScreenMagnificationController {
            mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
            mValueAnimator.setFloatValues(0.0f, 1.0f);
            mValueAnimator.addUpdateListener(this);
            mValueAnimator.addListener(this);
        }

        /**
@@ -1216,21 +1268,33 @@ public class FullScreenMagnificationController {
            }
        }

        public void updateSentSpecMainThread(MagnificationSpec spec, boolean animate) {
        void updateSentSpecMainThread(MagnificationSpec spec, Runnable endCallback) {
            if (mValueAnimator.isRunning()) {
                // Avoid AnimationEnd Callback.
                mEndCallback = null;
                mValueAnimator.cancel();
            }

            mEndCallback = endCallback;
            // If the current and sent specs don't match, update the sent spec.
            synchronized (mLock) {
                final boolean changed = !mSentMagnificationSpec.equals(spec);
                if (changed) {
                    if (animate) {
                    if (mEndCallback != null) {
                        animateMagnificationSpecLocked(spec);
                    } else {
                        setMagnificationSpecLocked(spec);
                    }
                } else {
                    sendEndCallbackMainThread();
                }
            }
        }

        private void sendEndCallbackMainThread() {
            if (mEndCallback != null) {
                mEndCallback.run();
                mEndCallback = null;
            }
        }

@@ -1270,6 +1334,26 @@ public class FullScreenMagnificationController {
                }
            }
        }

        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            sendEndCallbackMainThread();
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    }

    private static class ScreenStateObserver extends BroadcastReceiver {
@@ -1395,4 +1479,9 @@ public class FullScreenMagnificationController {
            return mAnimationDuration;
        }
    }

    @Nullable
    private static Runnable transformToStubRunnable(boolean animate) {
        return animate ? STUB_RUNNABLE : null;
    }
}
+99 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -95,12 +96,15 @@ public class FullScreenMagnificationControllerTest {

    ValueAnimator mMockValueAnimator;
    ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
    ValueAnimator.AnimatorListener mStateListener;

    FullScreenMagnificationController mFullScreenMagnificationController;
    Runnable mEndCallback;

    @Before
    public void setUp() {
        Looper looper = InstrumentationRegistry.getContext().getMainLooper();
        mEndCallback = Mockito.mock(Runnable.class);
        // Pretending ID of the Thread associated with looper as main thread ID in controller
        when(mMockContext.getMainLooper()).thenReturn(looper);
        when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
@@ -319,6 +323,7 @@ public class FullScreenMagnificationControllerTest {
        for (int i = 0; i < DISPLAY_COUNT; i++) {
            setScaleAndCenter_animated_stateChangesAndAnimationHappens(i);
            resetMockWindowManager();
            Mockito.reset(mEndCallback);
        }
    }

@@ -331,7 +336,7 @@ public class FullScreenMagnificationControllerTest {
        MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);

        assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
                newCenter.x, newCenter.y, true, SERVICE_ID_1));
                newCenter.x, newCenter.y, mEndCallback, SERVICE_ID_1));
        mMessageCapturingHandler.sendAllMessages();

        assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -358,7 +363,33 @@ public class FullScreenMagnificationControllerTest {
        Mockito.reset(mMockWindowManager);
        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
        mStateListener.onAnimationEnd(mMockValueAnimator);
        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
        verify(mEndCallback).run();
    }

    @Test
    public void testSetScaleAndCenterWithAnimation_sameSpec_noAnimationButInvokeEndCallback() {
        for (int i = 0; i < DISPLAY_COUNT; i++) {
            setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(i);
            Mockito.reset(mEndCallback);
        }
    }

    private void setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(int displayId) {
        register(displayId);
        final PointF center = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
        final float targetScale = 2.0f;
        assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
                targetScale, center.x, center.y, false, SERVICE_ID_1));
        mMessageCapturingHandler.sendAllMessages();

        assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
                targetScale, center.x, center.y, mEndCallback, SERVICE_ID_1));
        mMessageCapturingHandler.sendAllMessages();

        verify(mMockValueAnimator, never()).start();
        verify(mEndCallback).run();
    }

    @Test
@@ -638,6 +669,69 @@ public class FullScreenMagnificationControllerTest {
        assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
    }

    @Test
    public void testReset_notMagnifying_noStateChangeButInvokeCallback() {
        for (int i = 0; i < DISPLAY_COUNT; i++) {
            reset_notMagnifying_noStateChangeButInvokeCallback(i);
            Mockito.reset(mEndCallback);
        }
    }

    private void reset_notMagnifying_noStateChangeButInvokeCallback(int displayId) {
        register(displayId);

        assertFalse(mFullScreenMagnificationController.reset(displayId, mEndCallback));
        mMessageCapturingHandler.sendAllMessages();

        verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId),
                any(Region.class), anyFloat(), anyFloat(), anyFloat());
        verify(mEndCallback).run();
    }

    @Test
    public void testReset_Magnifying_resetsMagnificationAndInvokeLastEndCallback() {
        for (int i = 0; i < DISPLAY_COUNT; i++) {
            reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(i);
        }
    }

    private void reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(int displayId) {
        register(displayId);
        float scale = 2.5f;
        PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
        assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
                scale, firstCenter.x, firstCenter.y, mEndCallback, SERVICE_ID_1));
        mMessageCapturingHandler.sendAllMessages();
        Mockito.reset(mMockValueAnimator);
        // Stubs the logic after the animation is started.
        doAnswer(invocation -> {
            mStateListener.onAnimationEnd(mMockValueAnimator);
            return null;
        }).when(mMockValueAnimator).cancel();
        when(mMockValueAnimator.isRunning()).thenReturn(true);
        // Intermediate point
        float fraction = 0.33f;
        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
        Runnable lastEndCallback = Mockito.mock(Runnable.class);

        assertTrue(mFullScreenMagnificationController.reset(displayId, lastEndCallback));
        mMessageCapturingHandler.sendAllMessages();

        // Verify expected actions.
        verify(mEndCallback, never()).run();
        verify(mMockValueAnimator).start();
        verify(mMockValueAnimator).cancel();

        // Fast-forward the animation to the end.
        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
        mStateListener.onAnimationEnd(mMockValueAnimator);

        assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_0));
        verify(lastEndCallback).run();
    }

    @Test
    public void testTurnScreenOff_resetsMagnification() {
        register(DISPLAY_0);
@@ -1043,6 +1137,10 @@ public class FullScreenMagnificationControllerTest {
                ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
        verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
        mTargetAnimationListener = listenerArgumentCaptor.getValue();
        ArgumentCaptor<ValueAnimator.AnimatorListener> animatorListenerArgumentCaptor =
                ArgumentCaptor.forClass(ValueAnimator.AnimatorListener.class);
        verify(mMockValueAnimator).addListener(animatorListenerArgumentCaptor.capture());
        mStateListener = animatorListenerArgumentCaptor.getValue();
        Mockito.reset(mMockValueAnimator); // Ignore other initialization
    }