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
This commit is contained in:
ryanlwlin
2020-07-21 16:13:00 +08:00
parent 708294a7ad
commit b2671ade8e
9 changed files with 751 additions and 51 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,272 @@
/*
* 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
+ '}';
}
}
}

View File

@@ -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();
}
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;
}
}

View File

@@ -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

View File

@@ -0,0 +1,363 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.animation.ValueAnimator;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.view.SurfaceControl;
import android.view.animation.AccelerateInterpolator;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.atomic.AtomicReference;
@MediumTest
@RunWith(AndroidTestingRunner.class)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
private static final float DEFAULT_SCALE = 3.0f;
private static final float DEFAULT_CENTER_X = 400.0f;
private static final float DEFAULT_CENTER_Y = 500.0f;
// The duration couldn't too short, otherwise the ValueAnimator won't work in expectation.
private static final long ANIMATION_DURATION_MS = 200;
private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
private AtomicReference<Float> mCurrentCenterY = new AtomicReference<>((float) 0);
private ArgumentCaptor<Float> mScaleCaptor = ArgumentCaptor.forClass(Float.class);
private ArgumentCaptor<Float> mCenterXCaptor = ArgumentCaptor.forClass(Float.class);
private ArgumentCaptor<Float> mCenterYCaptor = ArgumentCaptor.forClass(Float.class);
@Mock
Handler mHandler;
@Mock
SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
WindowMagnifierCallback mWindowMagnifierCallback;
private SpyWindowMagnificationController mController;
private WindowMagnificationController mSpyController;
private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
private Instrumentation mInstrumentation;
private long mWaitingAnimationPeriod;
private long mWaitIntermediateAnimationPeriod;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mWaitingAnimationPeriod = ANIMATION_DURATION_MS + 50;
mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2;
mController = new SpyWindowMagnificationController(mContext, mHandler,
mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
mWindowMagnifierCallback);
mSpyController = mController.getSpyController();
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
mContext, mController, newValueAnimator());
}
@Test
public void enableWindowMagnification_disabled_expectedStartAndEndValues() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
verify(mSpyController, atLeast(2)).enableWindowMagnification(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
verifyStartValue(mScaleCaptor, 1.0f);
verifyStartValue(mCenterXCaptor, DEFAULT_CENTER_X);
verifyStartValue(mCenterYCaptor, DEFAULT_CENTER_Y);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@Test
public void enableWindowMagnification_enabling_expectedStartAndEndValues() {
enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
mInstrumentation.runOnMainSync(() -> {
Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY);
mCurrentScale.set(mController.getScale());
mCurrentCenterX.set(mController.getCenterX());
mCurrentCenterY.set(mController.getCenterY());
});
SystemClock.sleep(mWaitingAnimationPeriod);
verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
verifyStartValue(mScaleCaptor, mCurrentScale.get());
verifyStartValue(mCenterXCaptor, mCurrentCenterX.get());
verifyStartValue(mCenterYCaptor, mCurrentCenterY.get());
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
}
@Test
public void enableWindowMagnification_disabling_expectedStartAndEndValues() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
mInstrumentation.runOnMainSync(
() -> {
Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY);
mCurrentScale.set(mController.getScale());
mCurrentCenterX.set(mController.getCenterX());
mCurrentCenterY.set(mController.getCenterY());
});
SystemClock.sleep(mWaitingAnimationPeriod);
verify(mSpyController, atLeast(2)).enableWindowMagnification(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
//Animating in reverse, so we only check if the start values are greater than current.
assertTrue(mScaleCaptor.getAllValues().get(0) > mCurrentScale.get());
assertEquals(targetScale, mScaleCaptor.getValue(), 0f);
assertTrue(mCenterXCaptor.getAllValues().get(0) > mCurrentCenterX.get());
assertEquals(targetCenterX, mCenterXCaptor.getValue(), 0f);
assertTrue(mCenterYCaptor.getAllValues().get(0) > mCurrentCenterY.get());
assertEquals(targetCenterY, mCenterYCaptor.getValue(), 0f);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
}
@Test
public void enableWindowMagnificationWithSameScale_doNothing() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(),
anyFloat());
}
@Test
public void setScale_enabled_expectedScale() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
mInstrumentation.runOnMainSync(
() -> mWindowMagnificationAnimationController.setScale(DEFAULT_SCALE + 1));
verify(mSpyController).setScale(DEFAULT_SCALE + 1);
verifyFinalSpec(DEFAULT_SCALE + 1, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@Test
public void deleteWindowMagnification_enabled_expectedStartAndEndValues() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
verify(mSpyController).deleteWindowMagnification();
verifyStartValue(mScaleCaptor, DEFAULT_SCALE);
verifyStartValue(mCenterXCaptor, Float.NaN);
verifyStartValue(mCenterYCaptor, Float.NaN);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
}
@Test
public void deleteWindowMagnification_disabled_doNothing() {
deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
Mockito.verifyNoMoreInteractions(mSpyController);
}
@Test
public void deleteWindowMagnification_enabling_checkStartAndEndValues() {
enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
//It just reverse the animation, so we don't need to wait the whole duration.
mInstrumentation.runOnMainSync(
() -> {
Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.deleteWindowMagnification();
mCurrentScale.set(mController.getScale());
mCurrentCenterX.set(mController.getCenterX());
mCurrentCenterY.set(mController.getCenterY());
});
SystemClock.sleep(mWaitingAnimationPeriod);
verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
verify(mSpyController).deleteWindowMagnification();
//The animation is in verse, so we only check the start values should no be greater than
// the current one.
assertTrue(mScaleCaptor.getAllValues().get(0) <= mCurrentScale.get());
assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
verifyStartValue(mCenterXCaptor, Float.NaN);
verifyStartValue(mCenterYCaptor, Float.NaN);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
}
@Test
public void deleteWindowMagnification_disabling_checkStartAndValues() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
verify(mSpyController).deleteWindowMagnification();
assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
}
@Test
public void moveWindowMagnifier_enabled() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
mInstrumentation.runOnMainSync(
() -> mWindowMagnificationAnimationController.moveWindowMagnifier(100f, 200f));
verify(mSpyController).moveWindowMagnifier(100f, 200f);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f);
}
@Test
public void onConfigurationChanged_passThrough() {
mWindowMagnificationAnimationController.onConfigurationChanged(100);
verify(mSpyController).onConfigurationChanged(100);
}
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
assertEquals(expectedCenterX, mController.getCenterX(), 0f);
assertEquals(expectedCenterY, mController.getCenterY(), 0f);
}
private void enableWindowMagnificationAndWaitAnimating(long duration) {
mInstrumentation.runOnMainSync(
() -> {
Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
});
SystemClock.sleep(duration);
}
private void deleteWindowMagnificationAndWaitAnimating(long duration) {
mInstrumentation.runOnMainSync(
() -> {
resetMockObjects();
mWindowMagnificationAnimationController.deleteWindowMagnification();
});
SystemClock.sleep(duration);
}
private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
assertEquals(startValue, captor.getAllValues().get(0), 0f);
}
private void resetMockObjects() {
Mockito.reset(mSpyController);
}
/**
* It observes the methods in {@link WindowMagnificationController} since we couldn't spy it
* directly.
*/
private static class SpyWindowMagnificationController extends WindowMagnificationController {
private WindowMagnificationController mSpyController;
SpyWindowMagnificationController(Context context, Handler handler,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
WindowMagnifierCallback callback) {
super(context, handler, sfVsyncFrameProvider, mirrorWindowControl, transaction,
callback);
mSpyController = Mockito.mock(WindowMagnificationController.class);
}
WindowMagnificationController getSpyController() {
return mSpyController;
}
@Override
void enableWindowMagnification(float scale, float centerX, float centerY) {
super.enableWindowMagnification(scale, centerX, centerY);
mSpyController.enableWindowMagnification(scale, centerX, centerY);
}
@Override
void deleteWindowMagnification() {
super.deleteWindowMagnification();
mSpyController.deleteWindowMagnification();
}
@Override
void moveWindowMagnifier(float offsetX, float offsetY) {
super.moveWindowMagnifier(offsetX, offsetX);
mSpyController.moveWindowMagnifier(offsetX, offsetY);
}
@Override
void setScale(float scale) {
super.setScale(scale);
mSpyController.setScale(scale);
}
@Override
void onConfigurationChanged(int configDiff) {
super.onConfigurationChanged(configDiff);
mSpyController.onConfigurationChanged(configDiff);
}
}
private static ValueAnimator newValueAnimator() {
final ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(ANIMATION_DURATION_MS);
valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
valueAnimator.setFloatValues(0.0f, 1.0f);
return valueAnimator;
}
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.accessibility;
import static android.view.Choreographer.FrameCallback;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
@@ -26,8 +27,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
import androidx.test.InstrumentationRegistry;
@@ -41,6 +46,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -57,12 +63,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
WindowMagnifierCallback mWindowMagnifierCallback;
@Mock
SurfaceControl.Transaction mTransaction;
private Context mContext;
private WindowMagnificationController mWindowMagnificationController;
private Instrumentation mInstrumentation;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = Mockito.spy(getContext());
mInstrumentation = InstrumentationRegistry.getInstrumentation();
doAnswer(invocation -> {
FrameCallback callback = invocation.getArgument(0);
@@ -73,8 +81,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
when(mTransaction.remove(any())).thenReturn(mTransaction);
when(mTransaction.setGeometry(any(), any(), any(),
anyInt())).thenReturn(mTransaction);
mWindowMagnificationController = new WindowMagnificationController(getContext(),
mWindowMagnificationController = new WindowMagnificationController(mContext,
mHandler, mSfVsyncFrameProvider,
mMirrorWindowControl, mTransaction, mWindowMagnifierCallback);
verify(mMirrorWindowControl).setWindowDelegate(
@@ -83,9 +90,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@After
public void tearDown() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.deleteWindowMagnification();
});
mInstrumentation.runOnMainSync(
() -> mWindowMagnificationController.deleteWindowMagnification());
}
@Test
@@ -121,4 +127,27 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any());
}
@Test
public void setScale_enabled_expectedValue() {
mInstrumentation.runOnMainSync(
() -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN));
mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
}
@Test
public void onConfigurationChanged_disabled_withoutException() {
Display display = Mockito.spy(mContext.getDisplay());
when(display.getRotation()).thenReturn(Surface.ROTATION_90);
when(mContext.getDisplay()).thenReturn(display);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
});
}
}

View File

@@ -26,6 +26,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IWindowMagnificationConnection;
@@ -45,6 +46,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class WindowMagnificationTest extends SysuiTestCase {
@Mock

View File

@@ -25,10 +25,12 @@ import static java.util.Arrays.copyOfRange;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
import android.provider.Settings;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.view.Display;
import android.view.MotionEvent;
import com.android.internal.annotations.VisibleForTesting;
@@ -90,6 +92,8 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl
private MotionEventDispatcherDelegate mMotionEventDispatcherDelegate;
private final int mDisplayId;
private final Context mContext;
private final Point mTempPoint = new Point();
private final Queue<MotionEvent> mDebugOutputEventHistory;
@@ -107,7 +111,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl
Slog.i(LOG_TAG,
"WindowMagnificationGestureHandler() , displayId = " + displayId + ")");
}
mContext = context;
mWindowMagnificationMgr = windowMagnificationMgr;
mDetectShortcutTrigger = detectShortcutTrigger;
mDisplayId = displayId;
@@ -184,7 +188,14 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl
if (!mDetectShortcutTrigger) {
return;
}
toggleMagnification(Float.NaN, Float.NaN);
final Point screenSize = mTempPoint;
getScreenSize(mTempPoint);
toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f);
}
private void getScreenSize(Point outSize) {
final Display display = mContext.getDisplay();
display.getRealSize(outSize);
}
@Override