Merge "Add callback for transition animation"
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user