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

Commit b2671ade authored by ryanlwlin's avatar ryanlwlin
Browse files

Add WindowMagnificationAnimationController for animation

WindowMagnificationAnimationController provides same
functionality but it runs the animation while
enabling/disabling.

This new patch also fixs NPE excetion that happens
when device is in rotation but mirrorWindow is invisible.

Bug: 161669184 163026794 163035371
Test: atest com.android.systemui.accessibility
atest WindowMagnificationGestureHandlerTest
manual Test: enable window magnification to see it
        2. close mirroe window and rotate the device

Change-Id: I4531f5c96ea4b91b287cc8c5ffc4c98fc9611dd5
parent 708294a7
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback;
oneway interface IWindowMagnificationConnection {

    /**
     * Enables window magnification on specifed display with specified center and scale.
     * Enables window magnification on specified display with given center and scale and animation.
     *
     * @param displayId The logical display id.
     * @param scale magnification scale.
@@ -41,7 +41,7 @@ oneway interface IWindowMagnificationConnection {
    void enableWindowMagnification(int displayId, float scale, float centerX, float centerY);

    /**
     * Sets the scale of the window magnifier on specifed display.
     * Sets the scale of the window magnifier on specified display.
     *
     * @param displayId The logical display id.
     * @param scale magnification scale.
@@ -49,14 +49,14 @@ oneway interface IWindowMagnificationConnection {
    void setScale(int displayId, float scale);

     /**
     * Disables window magnification on specifed display.
     * Disables window magnification on specified display with animation.
     *
     * @param displayId The logical display id.
     */
    void disableWindowMagnification(int displayId);

    /**
     * Moves the window magnifier on the specifed display.
     * Moves the window magnifier on the specified display. It has no effect while animating.
     *
     * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
     *                current screen pixels.
+12 −23
Original line number Diff line number Diff line
@@ -53,8 +53,8 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
            ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION;

    @VisibleForTesting
    protected WindowMagnificationController mWindowMagnificationController;
    protected final ModeSwitchesController mModeSwitchesController;
    protected WindowMagnificationAnimationController mWindowMagnificationAnimationController;
    private final ModeSwitchesController mModeSwitchesController;
    private final Handler mHandler;
    private final AccessibilityManager mAccessibilityManager;
    private final CommandQueue mCommandQueue;
@@ -72,6 +72,11 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
                Context.ACCESSIBILITY_SERVICE);
        mCommandQueue = commandQueue;
        mModeSwitchesController = modeSwitchesController;
        final WindowMagnificationController controller = new WindowMagnificationController(mContext,
                mHandler, new SfVsyncFrameCallbackProvider(), null,
                new SurfaceControl.Transaction(), this);
        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                mContext, controller);
    }

    @Override
@@ -81,9 +86,7 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
            return;
        }
        mLastConfiguration.setTo(newConfig);
        if (mWindowMagnificationController != null) {
            mWindowMagnificationController.onConfigurationChanged(configDiff);
        }
        mWindowMagnificationAnimationController.onConfigurationChanged(configDiff);
        if (mModeSwitchesController != null) {
            mModeSwitchesController.onConfigurationChanged(configDiff);
        }
@@ -97,39 +100,25 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
    @MainThread
    void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
        //TODO: b/144080869 support multi-display.
        if (mWindowMagnificationController == null) {
            mWindowMagnificationController = new WindowMagnificationController(mContext,
                    mHandler,
                    new SfVsyncFrameCallbackProvider(),
                    null, new SurfaceControl.Transaction(),
                    this);
        }
        mWindowMagnificationController.enableWindowMagnification(scale, centerX, centerY);
        mWindowMagnificationAnimationController.enableWindowMagnification(scale, centerX, centerY);
    }

    @MainThread
    void setScale(int displayId, float scale) {
        //TODO: b/144080869 support multi-display.
        if (mWindowMagnificationController != null) {
            mWindowMagnificationController.setScale(scale);
        }
        mWindowMagnificationAnimationController.setScale(scale);
    }

    @MainThread
    void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
        //TODO: b/144080869 support multi-display.
        if (mWindowMagnificationController != null) {
            mWindowMagnificationController.moveWindowMagnifier(offsetX, offsetY);
        }
        mWindowMagnificationAnimationController.moveWindowMagnifier(offsetX, offsetY);
    }

    @MainThread
    void disableWindowMagnification(int displayId) {
        //TODO: b/144080869 support multi-display.
        if (mWindowMagnificationController != null) {
            mWindowMagnificationController.deleteWindowMagnification();
        }
        mWindowMagnificationController = null;
        mWindowMagnificationAnimationController.deleteWindowMagnification();
    }

    @Override
+272 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.view.animation.AccelerateInterpolator;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Provides same functionality of {@link WindowMagnificationController}. Some methods run with
 * the animation.
 */
class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUpdateListener,
        Animator.AnimatorListener {

    private static final String TAG = "WindowMagnificationBridge";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_DISABLING, STATE_ENABLING})
    @interface MagnificationState {}

    //The window magnification is disabled.
    private static final int STATE_DISABLED = 0;
    //The window magnification is enabled.
    private static final int STATE_ENABLED = 1;
    //The window magnification is going to be disabled when the animation is end.
    private  static final int STATE_DISABLING = 2;
    //The animation is running for enabling the window magnification.
    private static final int STATE_ENABLING = 3;

    private final WindowMagnificationController mController;
    private final ValueAnimator mValueAnimator;
    private final AnimationSpec mStartSpec = new AnimationSpec();
    private final AnimationSpec mEndSpec = new AnimationSpec();
    private final Context mContext;

    @MagnificationState
    private int mState = STATE_DISABLED;

    WindowMagnificationAnimationController(
            Context context, WindowMagnificationController controller) {
        this(context, controller, newValueAnimator(context.getResources()));
    }

    @VisibleForTesting
    WindowMagnificationAnimationController(Context context,
            WindowMagnificationController controller, ValueAnimator valueAnimator) {
        mContext = context;
        mController = controller;
        mValueAnimator = valueAnimator;
        mValueAnimator.addUpdateListener(this);
        mValueAnimator.addListener(this);
    }

    /**
     * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float)}
     * with transition animation. If the window magnification is not enabled, the scale will start
     * from 1.0 and the center won't be changed during the animation. If {@link #mState} is
     * {@code STATE_DISABLING}, the animation runs in reverse.
     *
     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged.
     * @param centerX the screen-relative X coordinate around which to center,
     *                or {@link Float#NaN} to leave unchanged.
     * @param centerY the screen-relative Y coordinate around which to center,
     *                or {@link Float#NaN} to leave unchanged.
     *
     * @see #onAnimationUpdate(ValueAnimator)
     */
    void enableWindowMagnification(float scale, float centerX, float centerY) {
        if (mState == STATE_ENABLING) {
            mValueAnimator.cancel();
        }
        setupEnableAnimationSpecs(scale, centerX, centerY);

        if (mEndSpec.equals(mStartSpec)) {
            setState(STATE_ENABLED);
        } else {
            if (mState == STATE_DISABLING) {
                mValueAnimator.reverse();
            } else {
                mValueAnimator.start();
            }
            setState(STATE_ENABLING);
        }
    }

    private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) {
        final float currentScale = mController.getScale();
        final float currentCenterX = mController.getCenterX();
        final float currentCenterY = mController.getCenterY();

        if (mState == STATE_DISABLED) {
            //We don't need to offset the center during the animation.
            mStartSpec.set(/* scale*/ 1.0f, centerX, centerY);
            mEndSpec.set(Float.isNaN(scale) ? mContext.getResources().getInteger(
                    R.integer.magnification_default_scale) : scale, centerX, centerY);
        } else {
            mStartSpec.set(currentScale, currentCenterX, currentCenterY);
            mEndSpec.set(Float.isNaN(scale) ? currentScale : scale,
                    Float.isNaN(centerX) ? currentCenterX : centerX,
                    Float.isNaN(centerY) ? currentCenterY : centerY);
        }
        if (DEBUG) {
            Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = "
                    + mEndSpec);
        }
    }

    /**
     * Wraps {@link WindowMagnificationController#setScale(float)}. If the animation is
     * running, it has no effect.
     */
    void setScale(float scale) {
        if (mValueAnimator.isRunning()) {
            return;
        }
        mController.setScale(scale);
    }

    /**
     * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
     * animation. If the window magnification is enabling, it runs the animation in reverse.
     */
    void deleteWindowMagnification() {
        if (mState == STATE_DISABLED || mState == STATE_DISABLING) {
            return;
        }
        mStartSpec.set(/* scale*/ 1.0f, Float.NaN, Float.NaN);
        mEndSpec.set(/* scale*/ mController.getScale(), Float.NaN, Float.NaN);

        mValueAnimator.reverse();
        setState(STATE_DISABLING);
    }

    /**
     * Wraps {@link WindowMagnificationController#moveWindowMagnifier(float, float)}. If the
     * animation is running, it has no effect.
     * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
     *                current screen pixels.
     * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
     *                current screen pixels.
     */
    void moveWindowMagnifier(float offsetX, float offsetY) {
        if (mValueAnimator.isRunning()) {
            return;
        }
        mController.moveWindowMagnifier(offsetX, offsetY);
    }

    void onConfigurationChanged(int configDiff) {
        mController.onConfigurationChanged(configDiff);
    }

    private void setState(@MagnificationState int state) {
        if (DEBUG) {
            Log.d(TAG, "setState from " + mState + " to " + state);
        }
        mState = state;
    }

    @Override
    public void onAnimationStart(Animator animation) {
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        if (mState == STATE_DISABLING) {
            mController.deleteWindowMagnification();
            setState(STATE_DISABLED);
        } else if (mState == STATE_ENABLING) {
            setState(STATE_ENABLED);
        } else {
            Log.w(TAG, "onAnimationEnd unexpected state:" + mState);
        }
    }

    @Override
    public void onAnimationCancel(Animator animation) {
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        final float fract = animation.getAnimatedFraction();
        final float sentScale = mStartSpec.mScale + (mEndSpec.mScale - mStartSpec.mScale) * fract;
        final float centerX =
                mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract;
        final float centerY =
                mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract;
        mController.enableWindowMagnification(sentScale, centerX, centerY);
    }

    private static ValueAnimator newValueAnimator(Resources resources) {
        final ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setDuration(
                resources.getInteger(com.android.internal.R.integer.config_longAnimTime));
        valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
        valueAnimator.setFloatValues(0.0f, 1.0f);
        return valueAnimator;
    }

    private static class AnimationSpec {
        private float mScale = Float.NaN;
        private float mCenterX = Float.NaN;
        private float mCenterY = Float.NaN;

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }

            if (other == null || getClass() != other.getClass()) {
                return false;
            }

            final AnimationSpec s = (AnimationSpec) other;
            return mScale == s.mScale && mCenterX == s.mCenterX && mCenterY == s.mCenterY;
        }

        @Override
        public int hashCode() {
            int result = (mScale != +0.0f ? Float.floatToIntBits(mScale) : 0);
            result = 31 * result + (mCenterX != +0.0f ? Float.floatToIntBits(mCenterX) : 0);
            result = 31 * result + (mCenterY != +0.0f ? Float.floatToIntBits(mCenterY) : 0);
            return result;
        }

        void set(float scale, float centerX, float centerY) {
            mScale = scale;
            mCenterX = centerX;
            mCenterY = centerY;
        }

        @Override
        public String toString() {
            return "AnimationSpec{"
                    + "mScale=" + mScale
                    + ", mCenterX=" + mCenterX
                    + ", mCenterY=" + mCenterY
                    + '}';
        }
    }
}
+42 −11
Original line number Diff line number Diff line
@@ -150,7 +150,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold

        mMirrorViewGeometryVsyncCallback =
                l -> {
                    if (mMirrorView != null && mMirrorSurface != null) {
                    if (isWindowVisible() && mMirrorSurface != null) {
                        calculateSourceBounds(mMagnificationFrame, mScale);
                        // The final destination for the magnification surface should be at 0,0
                        // since the ViewRootImpl's position will change
@@ -203,13 +203,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
     * @param configDiff a bit mask of the differences between the configurations
     */
    void onConfigurationChanged(int configDiff) {
        if (!isWindowVisible()) {
            return;
        }
        if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
            updateDimensions();
            // TODO(b/145780606): update toggle button UI.
            if (mMirrorView != null) {
            mWm.removeView(mMirrorView);
            createMirrorWindow();
            }
        } else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
            onRotate();
        }
@@ -502,7 +502,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
    /**
     * Enables window magnification with specified parameters.
     *
     * @param scale   the target scale
     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged
     * @param centerX the screen-relative X coordinate around which to center,
     *                or {@link Float#NaN} to leave unchanged.
     * @param centerY the screen-relative Y coordinate around which to center,
@@ -513,10 +513,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
                : centerX - mMagnificationFrame.exactCenterX();
        final float offsetY = Float.isNaN(centerY) ? 0
                : centerY - mMagnificationFrame.exactCenterY();
        mScale = scale;
        mScale = Float.isNaN(scale) ? mScale : scale;
        setMagnificationFrameBoundary();
        updateMagnificationFramePosition((int) offsetX, (int) offsetY);
        if (mMirrorView == null) {
        if (!isWindowVisible()) {
            createMirrorWindow();
            showControls();
        } else {
@@ -527,10 +527,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
    /**
     * Sets the scale of the magnified region if it's visible.
     *
     * @param scale the target scale
     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
     */
    void setScale(float scale) {
        if (mMirrorView == null || mScale == scale) {
        if (!isWindowVisible() || mScale == scale) {
            return;
        }
        enableWindowMagnification(scale, Float.NaN, Float.NaN);
@@ -552,4 +552,35 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
            modifyWindowMagnification(mTransaction);
        }
    }

    /**
     * Gets the scale.
     * @return {@link Float#NaN} if the window is invisible.
     */
    float getScale() {
        return isWindowVisible() ? mScale : Float.NaN;
    }

    /**
     * Returns the screen-relative X coordinate of the center of the magnified bounds.
     *
     * @return the X coordinate. {@link Float#NaN} if the window is invisible.
     */
    float getCenterX() {
        return isWindowVisible() ? mMagnificationFrame.exactCenterX() : Float.NaN;
    }

    /**
     * Returns the screen-relative Y coordinate of the center of the magnified bounds.
     *
     * @return the Y coordinate. {@link Float#NaN} if the window is invisible.
     */
    float getCenterY() {
        return isWindowVisible() ? mMagnificationFrame.exactCenterY() : Float.NaN;
    }

    //The window is visible when it is existed.
    private boolean isWindowVisible() {
        return mMirrorView != null;
    }
}
+9 −6
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.Context;
import android.os.RemoteException;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IWindowMagnificationConnection;
@@ -47,6 +48,7 @@ import org.mockito.MockitoAnnotations;
 */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class IWindowMagnificationConnectionTest extends SysuiTestCase {

    private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@@ -57,7 +59,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
    @Mock
    private IWindowMagnificationConnectionCallback mConnectionCallback;
    @Mock
    private WindowMagnificationController mWindowMagnificationController;
    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
    @Mock
    private ModeSwitchesController mModeSwitchesController;
    private IWindowMagnificationConnection mIWindowMagnificationConnection;
@@ -74,7 +76,8 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
                any(IWindowMagnificationConnection.class));
        mWindowMagnification = new WindowMagnification(getContext(),
                getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController);
        mWindowMagnification.mWindowMagnificationController = mWindowMagnificationController;
        mWindowMagnification.mWindowMagnificationAnimationController =
                mWindowMagnificationAnimationController;
        mWindowMagnification.requestWindowMagnificationConnection(true);
        assertNotNull(mIWindowMagnificationConnection);
        mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback);
@@ -86,7 +89,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
                Float.NaN);
        waitForIdleSync();

        verify(mWindowMagnificationController).enableWindowMagnification(3.0f, Float.NaN,
        verify(mWindowMagnificationAnimationController).enableWindowMagnification(3.0f, Float.NaN,
                Float.NaN);
    }

@@ -99,7 +102,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
        mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY);
        waitForIdleSync();

        verify(mWindowMagnificationController).deleteWindowMagnification();
        verify(mWindowMagnificationAnimationController).deleteWindowMagnification();
    }

    @Test
@@ -107,7 +110,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
        mIWindowMagnificationConnection.setScale(TEST_DISPLAY, 3.0f);
        waitForIdleSync();

        verify(mWindowMagnificationController).setScale(3.0f);
        verify(mWindowMagnificationAnimationController).setScale(3.0f);
    }

    @Test
@@ -115,7 +118,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
        mIWindowMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
        waitForIdleSync();

        verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
        verify(mWindowMagnificationAnimationController).moveWindowMagnifier(100f, 200f);
    }

    @Test
Loading