Merge "Add callback for transition animation"

This commit is contained in:
Ryan Lin
2020-08-04 01:50:06 +00:00
committed by Android (Google) Code Review
2 changed files with 205 additions and 18 deletions

View File

@@ -16,8 +16,10 @@
package com.android.server.accessibility.magnification;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -61,6 +63,8 @@ public class FullScreenMagnificationController {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "FullScreenMagnificationController";
private static final Runnable STUB_RUNNABLE = () -> {
};
public static final float MIN_SCALE = 1.0f;
public static final float MAX_SCALE = 8.0f;
@@ -292,7 +296,7 @@ public class FullScreenMagnificationController {
// Adjust the current spec's offsets if necessary.
if (updateCurrentSpecWithOffsetsLocked(
mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
sendSpecToAnimation(mCurrentMagnificationSpec, false);
sendSpecToAnimation(mCurrentMagnificationSpec, null);
}
onMagnificationChangedLocked();
}
@@ -300,17 +304,18 @@ public class FullScreenMagnificationController {
}
}
void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
void sendSpecToAnimation(MagnificationSpec spec, Runnable endCallback) {
if (DEBUG) {
Slog.i(LOG_TAG,
"sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")");
"sendSpecToAnimation(spec = " + spec + ", endCallback = " + endCallback
+ ")");
}
if (Thread.currentThread().getId() == mMainThreadId) {
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
mSpecAnimationBridge.updateSentSpecMainThread(spec, endCallback);
} else {
final Message m = PooledLambda.obtainMessage(
SpecAnimationBridge::updateSentSpecMainThread,
mSpecAnimationBridge, spec, animate);
mSpecAnimationBridge, spec, endCallback);
mControllerCtx.getHandler().sendMessage(m);
}
}
@@ -410,6 +415,11 @@ public class FullScreenMagnificationController {
@GuardedBy("mLock")
boolean reset(boolean animate) {
return reset(transformToStubRunnable(animate));
}
@GuardedBy("mLock")
boolean reset(Runnable endCallback) {
if (!mRegistered) {
return false;
}
@@ -420,7 +430,7 @@ public class FullScreenMagnificationController {
onMagnificationChangedLocked();
}
mIdOfLastServiceToMagnify = INVALID_ID;
sendSpecToAnimation(spec, animate);
sendSpecToAnimation(spec, endCallback);
return changed;
}
@@ -448,24 +458,24 @@ public class FullScreenMagnificationController {
final float centerX = normPivotX + offsetX;
final float centerY = normPivotY + offsetY;
mIdOfLastServiceToMagnify = id;
return setScaleAndCenter(scale, centerX, centerY, animate, id);
return setScaleAndCenter(scale, centerX, centerY, transformToStubRunnable(animate), id);
}
@GuardedBy("mLock")
boolean setScaleAndCenter(float scale, float centerX, float centerY,
boolean animate, int id) {
Runnable endCallback, int id) {
if (!mRegistered) {
return false;
}
if (DEBUG) {
Slog.i(LOG_TAG,
"setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+ ", centerY = " + centerY + ", animate = " + animate
+ ", centerY = " + centerY + ", endCallback = " + endCallback
+ ", id = " + id
+ ")");
}
final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
sendSpecToAnimation(mCurrentMagnificationSpec, animate);
sendSpecToAnimation(mCurrentMagnificationSpec, endCallback);
if (isMagnifying() && (id != INVALID_ID)) {
mIdOfLastServiceToMagnify = id;
}
@@ -531,7 +541,7 @@ public class FullScreenMagnificationController {
if (id != INVALID_ID) {
mIdOfLastServiceToMagnify = id;
}
sendSpecToAnimation(mCurrentMagnificationSpec, false);
sendSpecToAnimation(mCurrentMagnificationSpec, null);
}
boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
@@ -865,12 +875,26 @@ public class FullScreenMagnificationController {
* the spec did not change
*/
public boolean reset(int displayId, boolean animate) {
return reset(displayId, animate ? STUB_RUNNABLE : null);
}
/**
* Resets the magnification scale and center, optionally animating the
* transition.
*
* @param displayId The logical display id.
* @param endCallback Called when the animation is ended or the spec did not change.
* {@code null} to transition immediately
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean reset(int displayId, Runnable endCallback) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
if (display == null) {
return false;
}
return display.reset(animate);
return display.reset(endCallback);
}
}
@@ -921,7 +945,8 @@ public class FullScreenMagnificationController {
if (display == null) {
return false;
}
return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
return display.setScaleAndCenter(Float.NaN, centerX, centerY,
animate ? STUB_RUNNABLE : null, id);
}
}
@@ -944,12 +969,35 @@ public class FullScreenMagnificationController {
*/
public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
boolean animate, int id) {
return setScaleAndCenter(displayId, scale, centerX, centerY,
transformToStubRunnable(animate), id);
}
/**
* Sets the scale and center of the magnified region, optionally
* animating the transition. If animation is disabled, the transition
* is immediate.
*
* @param displayId The logical display id.
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
* @param centerY the screen-relative Y coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
* @param endCallback called when the transition is finished successfully or the spec did not
* change. {@code null} to transition immediately.
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
Runnable endCallback, int id) {
synchronized (mLock) {
final DisplayMagnification display = mDisplays.get(displayId);
if (display == null) {
return false;
}
return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
return display.setScaleAndCenter(scale, centerX, centerY, endCallback, id);
}
}
@@ -1160,7 +1208,8 @@ public class FullScreenMagnificationController {
* Class responsible for animating spec on the main thread and sending spec
* updates to the window manager.
*/
private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener,
Animator.AnimatorListener {
private final ControllerContext mControllerCtx;
/**
@@ -1180,6 +1229,8 @@ public class FullScreenMagnificationController {
*/
private final ValueAnimator mValueAnimator;
// Called when the callee wants animating and the sent spec matches the target spec.
private Runnable mEndCallback;
private final Object mLock;
private final int mDisplayId;
@@ -1197,6 +1248,7 @@ public class FullScreenMagnificationController {
mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
mValueAnimator.setFloatValues(0.0f, 1.0f);
mValueAnimator.addUpdateListener(this);
mValueAnimator.addListener(this);
}
/**
@@ -1216,24 +1268,36 @@ public class FullScreenMagnificationController {
}
}
public void updateSentSpecMainThread(MagnificationSpec spec, boolean animate) {
void updateSentSpecMainThread(MagnificationSpec spec, Runnable endCallback) {
if (mValueAnimator.isRunning()) {
// Avoid AnimationEnd Callback.
mEndCallback = null;
mValueAnimator.cancel();
}
mEndCallback = endCallback;
// If the current and sent specs don't match, update the sent spec.
synchronized (mLock) {
final boolean changed = !mSentMagnificationSpec.equals(spec);
if (changed) {
if (animate) {
if (mEndCallback != null) {
animateMagnificationSpecLocked(spec);
} else {
setMagnificationSpecLocked(spec);
}
} else {
sendEndCallbackMainThread();
}
}
}
private void sendEndCallbackMainThread() {
if (mEndCallback != null) {
mEndCallback.run();
mEndCallback = null;
}
}
@GuardedBy("mLock")
private void setMagnificationSpecLocked(MagnificationSpec spec) {
if (mEnabled) {
@@ -1270,6 +1334,26 @@ public class FullScreenMagnificationController {
}
}
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
sendEndCallbackMainThread();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
private static class ScreenStateObserver extends BroadcastReceiver {
@@ -1395,4 +1479,9 @@ public class FullScreenMagnificationController {
return mAnimationDuration;
}
}
@Nullable
private static Runnable transformToStubRunnable(boolean animate) {
return animate ? STUB_RUNNABLE : null;
}
}

View File

@@ -27,6 +27,7 @@ import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -95,12 +96,15 @@ public class FullScreenMagnificationControllerTest {
ValueAnimator mMockValueAnimator;
ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
ValueAnimator.AnimatorListener mStateListener;
FullScreenMagnificationController mFullScreenMagnificationController;
Runnable mEndCallback;
@Before
public void setUp() {
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
mEndCallback = Mockito.mock(Runnable.class);
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
@@ -319,6 +323,7 @@ public class FullScreenMagnificationControllerTest {
for (int i = 0; i < DISPLAY_COUNT; i++) {
setScaleAndCenter_animated_stateChangesAndAnimationHappens(i);
resetMockWindowManager();
Mockito.reset(mEndCallback);
}
}
@@ -331,7 +336,7 @@ public class FullScreenMagnificationControllerTest {
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
newCenter.x, newCenter.y, true, SERVICE_ID_1));
newCenter.x, newCenter.y, mEndCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -358,7 +363,33 @@ public class FullScreenMagnificationControllerTest {
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
mStateListener.onAnimationEnd(mMockValueAnimator);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
verify(mEndCallback).run();
}
@Test
public void testSetScaleAndCenterWithAnimation_sameSpec_noAnimationButInvokeEndCallback() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(i);
Mockito.reset(mEndCallback);
}
}
private void setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(int displayId) {
register(displayId);
final PointF center = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
final float targetScale = 2.0f;
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
targetScale, center.x, center.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
targetScale, center.x, center.y, mEndCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator, never()).start();
verify(mEndCallback).run();
}
@Test
@@ -638,6 +669,69 @@ public class FullScreenMagnificationControllerTest {
assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
}
@Test
public void testReset_notMagnifying_noStateChangeButInvokeCallback() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
reset_notMagnifying_noStateChangeButInvokeCallback(i);
Mockito.reset(mEndCallback);
}
}
private void reset_notMagnifying_noStateChangeButInvokeCallback(int displayId) {
register(displayId);
assertFalse(mFullScreenMagnificationController.reset(displayId, mEndCallback));
mMessageCapturingHandler.sendAllMessages();
verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId),
any(Region.class), anyFloat(), anyFloat(), anyFloat());
verify(mEndCallback).run();
}
@Test
public void testReset_Magnifying_resetsMagnificationAndInvokeLastEndCallback() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(i);
}
}
private void reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(int displayId) {
register(displayId);
float scale = 2.5f;
PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
scale, firstCenter.x, firstCenter.y, mEndCallback, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockValueAnimator);
// Stubs the logic after the animation is started.
doAnswer(invocation -> {
mStateListener.onAnimationEnd(mMockValueAnimator);
return null;
}).when(mMockValueAnimator).cancel();
when(mMockValueAnimator.isRunning()).thenReturn(true);
// Intermediate point
float fraction = 0.33f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
Runnable lastEndCallback = Mockito.mock(Runnable.class);
assertTrue(mFullScreenMagnificationController.reset(displayId, lastEndCallback));
mMessageCapturingHandler.sendAllMessages();
// Verify expected actions.
verify(mEndCallback, never()).run();
verify(mMockValueAnimator).start();
verify(mMockValueAnimator).cancel();
// Fast-forward the animation to the end.
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
mStateListener.onAnimationEnd(mMockValueAnimator);
assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_0));
verify(lastEndCallback).run();
}
@Test
public void testTurnScreenOff_resetsMagnification() {
register(DISPLAY_0);
@@ -1043,6 +1137,10 @@ public class FullScreenMagnificationControllerTest {
ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
mTargetAnimationListener = listenerArgumentCaptor.getValue();
ArgumentCaptor<ValueAnimator.AnimatorListener> animatorListenerArgumentCaptor =
ArgumentCaptor.forClass(ValueAnimator.AnimatorListener.class);
verify(mMockValueAnimator).addListener(animatorListenerArgumentCaptor.capture());
mStateListener = animatorListenerArgumentCaptor.getValue();
Mockito.reset(mMockValueAnimator); // Ignore other initialization
}