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

Commit 33ce8202 authored by Ryan Lin's avatar Ryan Lin Committed by Android (Google) Code Review
Browse files

Merge "Add WindowMagnificationBridge for animation"

parents dd3f81ff 1e14e16a
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
                    + '}';
        }
    }
}
+37 −6
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
@@ -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