/* * 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.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Instrumentation; import android.content.Context; import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.animation.AccelerateInterpolator; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.util.settings.SecureSettings; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @Ignore @LargeTest @RunWith(AndroidTestingRunner.class) public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { private static final float DEFAULT_SCALE = 4.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 = 300; private AtomicReference mCurrentScale = new AtomicReference<>((float) 0); private AtomicReference mCurrentCenterX = new AtomicReference<>((float) 0); private AtomicReference mCurrentCenterY = new AtomicReference<>((float) 0); private ArgumentCaptor mScaleCaptor = ArgumentCaptor.forClass(Float.class); private ArgumentCaptor mCenterXCaptor = ArgumentCaptor.forClass(Float.class); private ArgumentCaptor mCenterYCaptor = ArgumentCaptor.forClass(Float.class); private final ArgumentCaptor mOffsetXCaptor = ArgumentCaptor.forClass(Float.class); private final ArgumentCaptor mOffsetYCaptor = ArgumentCaptor.forClass(Float.class); @Mock Handler mHandler; @Mock SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; @Mock WindowMagnifierCallback mWindowMagnifierCallback; @Mock IRemoteMagnificationAnimationCallback mAnimationCallback; @Mock IRemoteMagnificationAnimationCallback mAnimationCallback2; @Mock(answer = Answers.RETURNS_SELF) SysUiState mSysUiState; @Mock SecureSettings mSecureSettings; private SpyWindowMagnificationController mController; private WindowMagnificationController mSpyController; private WindowMagnificationAnimationController mWindowMagnificationAnimationController; private Instrumentation mInstrumentation; private long mWaitingAnimationPeriod; private long mWaitIntermediateAnimationPeriod; private TestableWindowManager mWindowManager; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mInstrumentation = InstrumentationRegistry.getInstrumentation(); final WindowManager wm = mContext.getSystemService(WindowManager.class); mWindowManager = spy(new TestableWindowManager(wm)); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS; mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2; mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, newValueAnimator()); mController = new SpyWindowMagnificationController(mContext, mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(), mWindowMagnifierCallback, mSysUiState, mSecureSettings); mSpyController = mController.getSpyController(); } @After public void tearDown() throws Exception { mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification()); } @Test public void enableWindowMagnification_disabled_expectedValuesAndInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, 1.0f); verifyStartValue(mCenterXCaptor, DEFAULT_CENTER_X); verifyStartValue(mCenterYCaptor, DEFAULT_CENTER_Y); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); verify(mAnimationCallback).onResult(true); } @Test public void enableWindowMagnificationWithoutCallback_disabled_expectedValues() { enableWindowMagnificationWithoutAnimation(); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); } @Test public void enableWindowMagnificationWithoutCallback_enabled_expectedValues() { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); final float targetScale = DEFAULT_SCALE + 1.0f; final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; mInstrumentation.runOnMainSync( () -> { mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, targetCenterX, targetCenterY, null); }); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); } @Test public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback() throws RemoteException { mInstrumentation.runOnMainSync( () -> { mWindowMagnificationAnimationController.enableWindowMagnification(1, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, 0f, 0f); verify(mAnimationCallback).onResult(true); } @Test public void enableWindowMagnification_enabling_expectedValuesAndInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); 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, mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, mCurrentScale.get()); verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); } @Test public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(2); final MockMagnificationAnimationCallback animationCallback = new MockMagnificationAnimationCallback(countDownLatch); enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, animationCallback); mInstrumentation.runOnMainSync( () -> { mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN, animationCallback); }); assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); // The callback in 2nd enableWindowMagnification will return true assertEquals(1, animationCallback.getSuccessCount()); // The callback in 1st enableWindowMagnification will return false assertEquals(1, animationCallback.getFailedCount()); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); } @Test public void enableWindowMagnificationWithScaleOne_enabled_AnimationAndInvokeCallback() throws RemoteException { enableWindowMagnificationWithoutAnimation(); final float targetScale = 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, mAnimationCallback); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, mCurrentScale.get()); verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(true); assertEquals(WindowMagnificationAnimationController.STATE_ENABLED, mWindowMagnificationAnimationController.getState()); } @Test public void enableWindowMagnificationWithScaleLessThanOne_enabled_AnimationAndInvokeCallback() throws RemoteException { enableWindowMagnificationWithoutAnimation(); final float targetScale = 0.99f; final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, targetCenterX, targetCenterY, mAnimationCallback); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, mCurrentScale.get()); verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); // It presents the window magnification is disabled. verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(true); assertEquals(WindowMagnificationAnimationController.STATE_DISABLED, mWindowMagnificationAnimationController.getState()); } @Test public void enableWindowMagnificationWithScaleLessThanOneAndWithoutCallBack_enabled_expectedValues() throws RemoteException { enableWindowMagnificationWithoutAnimation(); final float targetScale = 0.99f; final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, targetCenterX, targetCenterY, null); }); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); assertEquals(WindowMagnificationAnimationController.STATE_DISABLED, mWindowMagnificationAnimationController.getState()); } @Test public void enableMagnificationWithoutCallback_enabling_expectedValuesAndInvokeFormerCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); 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, null); }); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(false); } @Test public void enableWindowMagnificationWithSameSpec_enabling_NoAnimationAndInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); } @Test public void enableWindowMagnification_disabling_expectedValuesAndInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); 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, mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); }); // Current spec shouldn't match given spec. verify(mAnimationCallback2, never()).onResult(anyBoolean()); verify(mAnimationCallback).onResult(false); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.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); verify(mAnimationCallback2).onResult(true); } @Test public void enableMagnificationWithoutCallback_disabling_expectedValuesAndInvokeFormerCallback() throws RemoteException { enableWindowMagnificationWithoutAnimation(); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); 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, null); }); verify(mAnimationCallback).onResult(false); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); } @Test public void enableWindowMagnificationWithSameSpec_disabling_NoAnimationAndInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); verify(mSpyController, never()).deleteWindowMagnification(); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); } @Test public void enableWindowMagnification_enabled_expectedValuesAndInvokeCallback() throws RemoteException { enableWindowMagnificationWithoutAnimation(); 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, mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, mCurrentScale.get()); verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback2).onResult(true); } @Test public void enableWindowMagnificationWithOffset_expectedValues() { final float offsetRatio = -0.1f; final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, windowBounds.exactCenterX(), windowBounds.exactCenterY(), offsetRatio, offsetRatio, mAnimationCallback); }); SystemClock.sleep(mWaitingAnimationPeriod); final View attachedView = mWindowManager.getAttachedView(); assertNotNull(attachedView); final Rect mirrorViewBound = new Rect(); final View mirrorView = attachedView.findViewById(R.id.surface_view); assertNotNull(mirrorView); mirrorView.getBoundsOnScreen(mirrorViewBound); assertEquals(mirrorViewBound.exactCenterX() - windowBounds.exactCenterX(), Math.round(offsetRatio * mirrorViewBound.width() / 2), 0.1f); assertEquals(mirrorViewBound.exactCenterY() - windowBounds.exactCenterY(), Math.round(offsetRatio * mirrorViewBound.height() / 2), 0.1f); } @Test public void moveWindowMagnifierToPosition_enabled_expectedValues() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(1); final MockMagnificationAnimationCallback animationCallback = new MockMagnificationAnimationCallback(countDownLatch); final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; enableWindowMagnificationWithoutAnimation(); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( targetCenterX, targetCenterY, animationCallback); }); assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); assertEquals(1, animationCallback.getSuccessCount()); assertEquals(0, animationCallback.getFailedCount()); verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); } @Test public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(4); final MockMagnificationAnimationCallback animationCallback = new MockMagnificationAnimationCallback(countDownLatch); enableWindowMagnificationWithoutAnimation(); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback); mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback); mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback); mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback); }); assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); // only the last one callback will return true assertEquals(1, animationCallback.getSuccessCount()); // the others will return false assertEquals(3, animationCallback.getFailedCount()); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40); } @Test public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(2); final MockMagnificationAnimationCallback animationCallback = new MockMagnificationAnimationCallback(countDownLatch); final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, animationCallback); mInstrumentation.runOnMainSync( () -> { mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( targetCenterX, targetCenterY, animationCallback); }); assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); // The callback in moveWindowMagnifierToPosition will return true assertEquals(1, animationCallback.getSuccessCount()); // The callback in enableWindowMagnification will return false assertEquals(1, animationCallback.getFailedCount()); verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); } @Test public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(2); final MockMagnificationAnimationCallback animationCallback = new MockMagnificationAnimationCallback(countDownLatch); enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, animationCallback); mInstrumentation.runOnMainSync( () -> { mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( Float.NaN, Float.NaN, animationCallback); }); assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); // The callback in moveWindowMagnifierToPosition will return true assertEquals(1, animationCallback.getSuccessCount()); // The callback in enableWindowMagnification will return false assertEquals(1, animationCallback.getFailedCount()); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); } @Test public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); verify(mAnimationCallback).onResult(true); } @Test public void deleteWindowMagnification_enabled_expectedValuesAndInvokeCallback() throws RemoteException { enableWindowMagnificationWithoutAnimation(); deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, DEFAULT_SCALE); verifyStartValue(mCenterXCaptor, Float.NaN); verifyStartValue(mCenterYCaptor, Float.NaN); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(true); } @Test public void deleteWindowMagnificationWithoutCallback_enabled_expectedValues() { enableWindowMagnificationWithoutAnimation(); deleteWindowMagnificationAndWaitAnimating(0, null); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); } @Test public void deleteWindowMagnification_disabled_doNothingAndInvokeCallback() throws RemoteException { deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); Mockito.verifyNoMoreInteractions(mSpyController); verify(mAnimationCallback).onResult(true); } @Test public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); mInstrumentation.runOnMainSync( () -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.deleteWindowMagnification( mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); //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); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); } @Test public void deleteWindowMagnificationWithoutCallback_enabling_expectedValuesAndInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); mInstrumentation.runOnMainSync( () -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.deleteWindowMagnification(null); }); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); } @Test public void deleteWindowMagnification_disabling_checkStartAndValues() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback2); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); assertEquals(1.0f, mScaleCaptor.getValue(), 0f); verifyStartValue(mOffsetXCaptor, 0f); verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); } @Test public void deleteWindowMagnificationWithoutCallback_disabling_checkStartAndValues() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, mAnimationCallback); deleteWindowMagnificationAndWaitAnimating(0, null); verify(mSpyController).deleteWindowMagnification(); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); } @Test public void moveWindowMagnifier_enabled_vertical_only_expectedValue() { enableWindowMagnificationWithoutAnimation(); // should move vertically since offsetY/offsetX > HORIZONTAL_LOCK_BASE final float offsetX = 50.0f; final float offsetY = (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE) + 1.0f; mInstrumentation.runOnMainSync( () -> mController.moveWindowMagnifier(offsetX, offsetY)); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY); } @Test public void moveWindowMagnifier_enabled_horinzontal_only_expectedValue() { enableWindowMagnificationWithoutAnimation(); // should move vertically since offsetY/offsetX <= HORIZONTAL_LOCK_BASE final float offsetX = 50.0f; final float offsetY = (float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE) - 1.0f; mInstrumentation.runOnMainSync( () -> mController.moveWindowMagnifier(offsetX, offsetY)); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y); } @Test public void moveWindowMagnifier_enabled_setDiagonalEnabled_expectedValues() { enableWindowMagnificationWithoutAnimation(); final float offsetX = 50.0f; final float offsetY = (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE); // while diagonal scrolling enabled, // should move with both offsetX and offsetY without regrading offsetY/offsetX mInstrumentation.runOnMainSync( () -> { mController.setDiagonalScrolling(true); mController.moveWindowMagnifier(offsetX, offsetY); }); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y + offsetY); } @Test public void moveWindowMagnifierToPosition_enabled() { final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; enableWindowMagnificationWithoutAnimation(); mInstrumentation.runOnMainSync( () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY, mAnimationCallback)); SystemClock.sleep(mWaitingAnimationPeriod); verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); } 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 enableWindowMagnificationWithoutAnimation() { mInstrumentation.runOnMainSync( () -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null); }); } private void enableWindowMagnificationAndWaitAnimating(long duration, @Nullable IRemoteMagnificationAnimationCallback callback) { mInstrumentation.runOnMainSync( () -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback); }); SystemClock.sleep(duration); } private void deleteWindowMagnificationAndWaitAnimating(long duration, @Nullable IRemoteMagnificationAnimationCallback callback) { mInstrumentation.runOnMainSync( () -> { resetMockObjects(); mWindowMagnificationAnimationController.deleteWindowMagnification(callback); }); SystemClock.sleep(duration); } private void verifyStartValue(ArgumentCaptor 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, WindowMagnificationAnimationController animationController, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction, WindowMagnifierCallback callback, SysUiState sysUiState, SecureSettings secureSettings) { super( context, handler, animationController, sfVsyncFrameProvider, mirrorWindowControl, transaction, callback, sysUiState, WindowManagerGlobal::getWindowSession, secureSettings); mSpyController = Mockito.mock(WindowMagnificationController.class); } WindowMagnificationController getSpyController() { return mSpyController; } @Override void enableWindowMagnificationInternal(float scale, float centerX, float centerY) { super.enableWindowMagnificationInternal(scale, centerX, centerY); mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY); } @Override void enableWindowMagnificationInternal(float scale, float centerX, float centerY, float magnificationOffsetFrameRatioX, float magnificationOffsetFrameRatioY) { super.enableWindowMagnificationInternal(scale, centerX, centerY, magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY); mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY, magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY); } @Override void deleteWindowMagnification() { super.deleteWindowMagnification(); mSpyController.deleteWindowMagnification(); } @Override void moveWindowMagnifier(float offsetX, float offsetY) { super.moveWindowMagnifier(offsetX, offsetY); mSpyController.moveWindowMagnifier(offsetX, offsetY); } @Override void moveWindowMagnifierToPosition(float positionX, float positionY, IRemoteMagnificationAnimationCallback callback) { super.moveWindowMagnifierToPosition(positionX, positionY, callback); mSpyController.moveWindowMagnifierToPosition(positionX, positionY, callback); } @Override void setScale(float scale) { super.setScale(scale); mSpyController.setScale(scale); } @Override public void updateSysUIStateFlag() { super.updateSysUIStateFlag(); mSpyController.updateSysUIStateFlag(); } } 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; } }