blob: 33b1d94df80e367affc2cbdd75732a7545769d60 [file] [log] [blame]
/*
* 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.anyBoolean;
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.annotation.Nullable;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.view.SurfaceControl;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
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.After;
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;
@Mock
IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock
IRemoteMagnificationAnimationCallback mAnimationCallback2;
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());
}
@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)).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);
verify(mAnimationCallback).onResult(true);
}
@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).enableWindowMagnification(1, DEFAULT_CENTER_X,
DEFAULT_CENTER_Y);
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)).enableWindowMagnification(mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
verifyStartValue(mScaleCaptor, mCurrentScale.get());
verifyStartValue(mCenterXCaptor, mCurrentCenterX.get());
verifyStartValue(mCenterYCaptor, mCurrentCenterY.get());
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
verify(mAnimationCallback2).onResult(true);
}
@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()).enableWindowMagnification(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)).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);
verify(mAnimationCallback2).onResult(true);
}
@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()).enableWindowMagnification(anyFloat(), anyFloat(),
anyFloat());
verify(mSpyController, never()).deleteWindowMagnification();
verify(mAnimationCallback).onResult(false);
verify(mAnimationCallback2).onResult(true);
}
@Test
public void enableWindowMagnification_enabled_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)).enableWindowMagnification(mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture());
verifyStartValue(mScaleCaptor, mCurrentScale.get());
verifyStartValue(mCenterXCaptor, mCurrentCenterX.get());
verifyStartValue(mCenterYCaptor, mCurrentCenterY.get());
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
verify(mAnimationCallback2).onResult(true);
}
@Test
public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
throws RemoteException {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(),
anyFloat());
verify(mAnimationCallback).onResult(true);
}
@Test
public void setScale_enabled_expectedScale() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
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_expectedValuesAndInvokeCallback()
throws RemoteException {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
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);
verify(mAnimationCallback).onResult(true);
}
@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)).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);
verify(mAnimationCallback).onResult(false);
verify(mAnimationCallback2).onResult(true);
}
@Test
public void deleteWindowMagnification_disabling_checkStartAndValues() throws RemoteException {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
mAnimationCallback);
deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback2);
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);
verify(mAnimationCallback).onResult(false);
verify(mAnimationCallback2).onResult(true);
}
@Test
public void moveWindowMagnifier_enabled() {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
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,
@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<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;
}
}