diff options
| author | 2020-09-16 01:13:03 +0000 | |
|---|---|---|
| committer | 2020-09-16 01:13:03 +0000 | |
| commit | 28a10589ef7f6d6fff051ac6082dda1d2444d397 (patch) | |
| tree | 45319aa0e867076622a4cbdf15986d1cb3387cc8 | |
| parent | f50b1540d88e122de84e67255060152e53a1de57 (diff) | |
| parent | 379e238c6ba2780f67205665292ec909b92a6ca5 (diff) | |
Merge changes Ic12d6d90,Iecd5e1bc
* changes:
Implement transitn animation while switching the mode
Change magnification animation callback (2/2)
16 files changed, 953 insertions, 172 deletions
diff --git a/core/java/android/view/accessibility/IRemoteMagnificationAnimationCallback.aidl b/core/java/android/view/accessibility/IRemoteMagnificationAnimationCallback.aidl new file mode 100644 index 000000000000..946ea16575bd --- /dev/null +++ b/core/java/android/view/accessibility/IRemoteMagnificationAnimationCallback.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 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 android.view.accessibility; + +/** + * A callback for magnification animation result. + * @hide + */ + + oneway interface IRemoteMagnificationAnimationCallback { + + /** + * Called when the animation is finished or interrupted during animating. + * + * @param success {@code true} if animating successfully with given spec or the spec did not + * change. Otherwise {@code false} + */ + void onResult(boolean success); +} diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl index ae853e952d25..ddf68fcb1311 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl +++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl @@ -18,8 +18,8 @@ package android.view.accessibility; import android.graphics.PointF; import android.graphics.Rect; -import android.os.RemoteCallback; import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; /** * Interface for interaction between {@link AccessibilityManagerService} @@ -38,10 +38,10 @@ oneway interface IWindowMagnificationConnection { * 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. - * @param endCallback The callback called when the animation is completed. + * @param callback The callback called when the animation is completed or interrupted. */ void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, - in RemoteCallback endCallback); + in IRemoteMagnificationAnimationCallback callback); /** * Sets the scale of the window magnifier on specified display. @@ -55,9 +55,10 @@ oneway interface IWindowMagnificationConnection { * Disables window magnification on specified display with animation. * * @param displayId The logical display id. - * @param endCallback The callback called when the animation is completed. + * @param callback The callback called when the animation is completed or interrupted. */ - void disableWindowMagnification(int displayId, in RemoteCallback endCallback); + void disableWindowMagnification(int displayId, + in IRemoteMagnificationAnimationCallback callback); /** * Moves the window magnifier on the specified display. It has no effect while animating. diff --git a/core/java/android/view/accessibility/MagnificationAnimationCallback.java b/core/java/android/view/accessibility/MagnificationAnimationCallback.java index 491f7fb32a8c..bc9fb0a3e5e0 100644 --- a/core/java/android/view/accessibility/MagnificationAnimationCallback.java +++ b/core/java/android/view/accessibility/MagnificationAnimationCallback.java @@ -28,4 +28,4 @@ public interface MagnificationAnimationCallback { * change. Otherwise {@code false} */ void onResult(boolean success); -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index e10d2be4cb5e..911bf9ef757b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -24,11 +24,11 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Handler; -import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Log; import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; @@ -101,10 +101,10 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall @MainThread void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, - @Nullable RemoteCallback endCallback) { + @Nullable IRemoteMagnificationAnimationCallback callback) { //TODO: b/144080869 support multi-display. mWindowMagnificationAnimationController.enableWindowMagnification(scale, centerX, centerY, - endCallback != null ? () -> endCallback.sendResult(null) : null); + callback); } @MainThread @@ -120,10 +120,10 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall } @MainThread - void disableWindowMagnification(int displayId, @Nullable RemoteCallback endCallback) { + void disableWindowMagnification(int displayId, + @Nullable IRemoteMagnificationAnimationCallback callback) { //TODO: b/144080869 support multi-display. - mWindowMagnificationAnimationController.deleteWindowMagnification( - endCallback != null ? () -> endCallback.sendResult(null) : null); + mWindowMagnificationAnimationController.deleteWindowMagnification(callback); } @Override @@ -182,10 +182,10 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall @Override public void enableWindowMagnification(int displayId, float scale, float centerX, - float centerY, RemoteCallback remoteCallback) { + float centerY, IRemoteMagnificationAnimationCallback callback) { mHandler.post( () -> mWindowMagnification.enableWindowMagnification(displayId, scale, centerX, - centerY, remoteCallback)); + centerY, callback)); } @Override @@ -194,9 +194,10 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall } @Override - public void disableWindowMagnification(int displayId, RemoteCallback remoteCallback) { + public void disableWindowMagnification(int displayId, + IRemoteMagnificationAnimationCallback callback) { mHandler.post(() -> mWindowMagnification.disableWindowMagnification(displayId, - remoteCallback)); + callback)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java index 2f2e3eaddd3b..24d83884f093 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java @@ -22,7 +22,9 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; +import android.os.RemoteException; import android.util.Log; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.animation.AccelerateInterpolator; import com.android.internal.annotations.VisibleForTesting; @@ -38,7 +40,7 @@ import java.lang.annotation.RetentionPolicy; class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { - private static final String TAG = "WindowMagnificationBridge"; + private static final String TAG = "WindowMagnificationAnimationController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @Retention(RetentionPolicy.SOURCE) @@ -61,7 +63,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp private final Context mContext; // Called when the animation is ended successfully without cancelling or mStartSpec and // mEndSpec are equal. - private Runnable mAnimationEndCallback; + private IRemoteMagnificationAnimationCallback mAnimationCallback; // The flag to ignore the animation end callback. private boolean mEndAnimationCanceled = false; @MagnificationState @@ -93,14 +95,16 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp * 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. - * @param animationEndCallback Called when the transition is complete or the given arguments - * are as same as current values. + * @param animationCallback Called when the transition is complete, the given arguments + * are as same as current values, or the transition is interrupted + * due to the new transition request. * * @see #onAnimationUpdate(ValueAnimator) */ void enableWindowMagnification(float scale, float centerX, float centerY, - @Nullable Runnable animationEndCallback) { - mAnimationEndCallback = animationEndCallback; + @Nullable IRemoteMagnificationAnimationCallback animationCallback) { + sendAnimationCallback(false); + mAnimationCallback = animationCallback; setupEnableAnimationSpecs(scale, centerX, centerY); if (mEndSpec.equals(mStartSpec)) { if (mState == STATE_DISABLED) { @@ -108,7 +112,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp } else if (mState == STATE_ENABLING || mState == STATE_DISABLING) { mValueAnimator.cancel(); } - sendCallbackIfNeeded(); + sendAnimationCallback(true); setState(STATE_ENABLED); } else { if (mState == STATE_DISABLING) { @@ -160,14 +164,17 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition * animation. If the window magnification is enabling, it runs the animation in reverse. * - * @param animationEndCallback Called when the transition is complete or the window - * magnification is disabled already. + * @param animationCallback Called when the transition is complete, the given arguments + * are as same as current values, or the transition is interrupted + * due to the new transition request. */ - void deleteWindowMagnification(@Nullable Runnable animationEndCallback) { - mAnimationEndCallback = animationEndCallback; + void deleteWindowMagnification( + @Nullable IRemoteMagnificationAnimationCallback animationCallback) { + sendAnimationCallback(false); + mAnimationCallback = animationCallback; if (mState == STATE_DISABLED || mState == STATE_DISABLING) { if (mState == STATE_DISABLED) { - sendCallbackIfNeeded(); + sendAnimationCallback(true); } return; } @@ -220,7 +227,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp } else { setState(STATE_ENABLED); } - sendCallbackIfNeeded(); + sendAnimationCallback(true); } @Override @@ -236,10 +243,17 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp public void onAnimationRepeat(Animator animation) { } - private void sendCallbackIfNeeded() { - if (mAnimationEndCallback != null) { - mAnimationEndCallback.run(); - mAnimationEndCallback = null; + private void sendAnimationCallback(boolean success) { + if (mAnimationCallback != null) { + try { + mAnimationCallback.onResult(success); + if (DEBUG) { + Log.d(TAG, "sendAnimationCallback success = " + success); + } + } catch (RemoteException e) { + Log.w(TAG, "sendAnimationCallback failed : " + e); + } + mAnimationCallback = null; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java index 9079338cd502..1e969c226ff1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java @@ -23,13 +23,13 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import android.content.Context; -import android.os.RemoteCallback; 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.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; @@ -41,7 +41,6 @@ import com.android.systemui.statusbar.CommandQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -66,8 +65,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Mock private ModeSwitchesController mModeSwitchesController; @Mock - private RemoteCallback mRemoteCallback; - private ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + private IRemoteMagnificationAnimationCallback mAnimationCallback; private IWindowMagnificationConnection mIWindowMagnificationConnection; private WindowMagnification mWindowMagnification; @@ -92,22 +90,21 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void enableWindowMagnification_passThrough() throws RemoteException { mIWindowMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN, - Float.NaN, mRemoteCallback); + Float.NaN, mAnimationCallback); waitForIdleSync(); verify(mWindowMagnificationAnimationController).enableWindowMagnification(eq(3.0f), - eq(Float.NaN), eq(Float.NaN), mRunnableCaptor.capture()); - verifyRunnableWrapsRemoteCallback(mRunnableCaptor.getValue()); + eq(Float.NaN), eq(Float.NaN), eq(mAnimationCallback)); } @Test public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException { - mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY, mRemoteCallback); + mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY, + mAnimationCallback); waitForIdleSync(); verify(mWindowMagnificationAnimationController).deleteWindowMagnification( - mRunnableCaptor.capture()); - verifyRunnableWrapsRemoteCallback(mRunnableCaptor.getValue()); + mAnimationCallback); } @Test @@ -143,10 +140,5 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { verify(mModeSwitchesController).removeButton(TEST_DISPLAY); } - - private void verifyRunnableWrapsRemoteCallback(Runnable runnable) { - runnable.run(); - verify(mRemoteCallback).sendResult(null); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index d74c62b90fda..33b1d94df80e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -18,6 +18,7 @@ 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; @@ -28,9 +29,11 @@ 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; @@ -75,9 +78,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Mock WindowMagnifierCallback mWindowMagnifierCallback; @Mock - Runnable mAnimationEndCallback; + IRemoteMagnificationAnimationCallback mAnimationCallback; @Mock - Runnable mAnimationEndCallback2; + IRemoteMagnificationAnimationCallback mAnimationCallback2; private SpyWindowMagnificationController mController; private WindowMagnificationController mSpyController; private WindowMagnificationAnimationController mWindowMagnificationAnimationController; @@ -105,8 +108,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } @Test - public void enableWindowMagnification_disabled_expectedValuesAndInvokeCallback() { - enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationEndCallback); + public void enableWindowMagnification_disabled_expectedValuesAndInvokeCallback() + throws RemoteException { + enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); verify(mSpyController, atLeast(2)).enableWindowMagnification( mScaleCaptor.capture(), @@ -115,28 +119,30 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { verifyStartValue(mCenterXCaptor, DEFAULT_CENTER_X); verifyStartValue(mCenterYCaptor, DEFAULT_CENTER_Y); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); - verify(mAnimationEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test - public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback() { + public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback() + throws RemoteException { mInstrumentation.runOnMainSync( () -> { mWindowMagnificationAnimationController.enableWindowMagnification(1, - DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationEndCallback); + DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController).enableWindowMagnification(1, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); - verify(mAnimationEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test - public void enableWindowMagnification_enabling_expectedValuesAndInvokeCallback() { + public void enableWindowMagnification_enabling_expectedValuesAndInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, - mAnimationEndCallback); + mAnimationCallback); final float targetScale = DEFAULT_SCALE + 1.0f; final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; @@ -144,7 +150,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationEndCallback2); + targetCenterX, targetCenterY, mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); @@ -158,33 +164,35 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); - verify(mAnimationEndCallback, never()).run(); - verify(mAnimationEndCallback2).run(); + verify(mAnimationCallback).onResult(false); + verify(mAnimationCallback2).onResult(true); } @Test - public void enableWindowMagnificationWithSameSpec_enabling_NoAnimationAndInvokeCallback() { + public void enableWindowMagnificationWithSameSpec_enabling_NoAnimationAndInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, - mAnimationEndCallback); + mAnimationCallback); mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, - Float.NaN, Float.NaN, mAnimationEndCallback2); + Float.NaN, Float.NaN, mAnimationCallback2); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(), anyFloat()); - verify(mAnimationEndCallback, never()).run(); - verify(mAnimationEndCallback2).run(); + verify(mAnimationCallback).onResult(false); + verify(mAnimationCallback2).onResult(true); } @Test - public void enableWindowMagnification_disabling_expectedValuesAndInvokeCallback() { + public void enableWindowMagnification_disabling_expectedValuesAndInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, - mAnimationEndCallback); + mAnimationCallback); final float targetScale = DEFAULT_SCALE + 1.0f; final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; @@ -193,13 +201,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { () -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationEndCallback2); + targetCenterX, targetCenterY, mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); }); // Current spec shouldn't match given spec. - verify(mAnimationEndCallback2, never()).run(); + verify(mAnimationCallback2, never()).onResult(anyBoolean()); + verify(mAnimationCallback).onResult(false); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, atLeast(2)).enableWindowMagnification( @@ -213,34 +222,35 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { assertTrue(mCenterYCaptor.getAllValues().get(0) > mCurrentCenterY.get()); assertEquals(targetCenterY, mCenterYCaptor.getValue(), 0f); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); - verify(mAnimationEndCallback, never()).run(); - verify(mAnimationEndCallback2).run(); + verify(mAnimationCallback2).onResult(true); } @Test - public void enableWindowMagnificationWithSameSpec_disabling_NoAnimationAndInvokeCallback() { + public void enableWindowMagnificationWithSameSpec_disabling_NoAnimationAndInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, - mAnimationEndCallback); + mAnimationCallback); mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, - Float.NaN, Float.NaN, mAnimationEndCallback2); + Float.NaN, Float.NaN, mAnimationCallback2); }); SystemClock.sleep(mWaitingAnimationPeriod); verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(), anyFloat()); verify(mSpyController, never()).deleteWindowMagnification(); - verify(mAnimationEndCallback, never()).run(); - verify(mAnimationEndCallback2).run(); + verify(mAnimationCallback).onResult(false); + verify(mAnimationCallback2).onResult(true); } @Test - public void enableWindowMagnification_enabled_expectedValuesAndInvokeCallback() { + public void enableWindowMagnification_enabled_expectedValuesAndInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, - mAnimationEndCallback); + mAnimationCallback); final float targetScale = DEFAULT_SCALE + 1.0f; final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; @@ -248,7 +258,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationEndCallback2); + targetCenterX, targetCenterY, mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); @@ -262,19 +272,20 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); - verify(mAnimationEndCallback, never()).run(); - verify(mAnimationEndCallback2).run(); + verify(mAnimationCallback).onResult(false); + verify(mAnimationCallback2).onResult(true); } @Test - public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback() { + public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); - enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationEndCallback); + enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(), anyFloat()); - verify(mAnimationEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test @@ -289,10 +300,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } @Test - public void deleteWindowMagnification_enabled_expectedValuesAndInvokeCallback() { + public void deleteWindowMagnification_enabled_expectedValuesAndInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); - deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationEndCallback); + deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture()); @@ -301,27 +313,29 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { verifyStartValue(mCenterXCaptor, Float.NaN); verifyStartValue(mCenterYCaptor, Float.NaN); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); - verify(mAnimationEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test - public void deleteWindowMagnification_disabled_doNothingAndInvokeCallback() { - deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationEndCallback); + public void deleteWindowMagnification_disabled_doNothingAndInvokeCallback() + throws RemoteException { + deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); Mockito.verifyNoMoreInteractions(mSpyController); - verify(mAnimationEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test - public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback() { + public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback() + throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, - mAnimationEndCallback); + mAnimationCallback); mInstrumentation.runOnMainSync( () -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.deleteWindowMagnification( - mAnimationEndCallback2); + mAnimationCallback2); mCurrentScale.set(mController.getScale()); mCurrentCenterX.set(mController.getCenterX()); mCurrentCenterY.set(mController.getCenterY()); @@ -339,25 +353,25 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { verifyStartValue(mCenterXCaptor, Float.NaN); verifyStartValue(mCenterYCaptor, Float.NaN); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); - verify(mAnimationEndCallback, never()).run(); - verify(mAnimationEndCallback2).run(); + verify(mAnimationCallback).onResult(false); + verify(mAnimationCallback2).onResult(true); } @Test - public void deleteWindowMagnification_disabling_checkStartAndValues() { + public void deleteWindowMagnification_disabling_checkStartAndValues() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, - mAnimationEndCallback); + mAnimationCallback); - deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationEndCallback2); + 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(mAnimationEndCallback, never()).run(); - verify(mAnimationEndCallback2).run(); + verify(mAnimationCallback).onResult(false); + verify(mAnimationCallback2).onResult(true); } @Test @@ -386,22 +400,22 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } private void enableWindowMagnificationAndWaitAnimating(long duration, - @Nullable Runnable endCallback) { + @Nullable IRemoteMagnificationAnimationCallback callback) { mInstrumentation.runOnMainSync( () -> { Mockito.reset(mSpyController); mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, - DEFAULT_CENTER_X, DEFAULT_CENTER_Y, endCallback); + DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback); }); SystemClock.sleep(duration); } private void deleteWindowMagnificationAndWaitAnimating(long duration, - @Nullable Runnable endCallback) { + @Nullable IRemoteMagnificationAnimationCallback callback) { mInstrumentation.runOnMainSync( () -> { resetMockObjects(); - mWindowMagnificationAnimationController.deleteWindowMagnification(endCallback); + mWindowMagnificationAnimationController.deleteWindowMagnification(callback); }); SystemClock.sleep(duration); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index c41f4007aa59..80e9703e0e62 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -120,6 +120,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.MagnificationGestureHandler; +import com.android.server.accessibility.magnification.MagnificationTransitionController; import com.android.server.accessibility.magnification.WindowMagnificationManager; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -258,6 +259,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private Point mTempPoint = new Point(); private boolean mIsAccessibilityButtonShown; + private MagnificationTransitionController mMagnificationTransitionController; private AccessibilityUserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); @@ -302,6 +304,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager = a11yWindowManager; mA11yDisplayListener = a11yDisplayListener; mWindowMagnificationMgr = windowMagnificationMgr; + mMagnificationTransitionController = new MagnificationTransitionController(this, mLock); init(); } @@ -321,6 +324,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); + mMagnificationTransitionController = new MagnificationTransitionController(this, mLock); init(); } @@ -1550,9 +1554,26 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (fallBackMagnificationModeSettingsLocked(userState)) { return; } - mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::notifyRefreshMagnificationModeToInputFilter, - this)); + mMagnificationTransitionController.transitionMagnificationModeLocked( + Display.DEFAULT_DISPLAY, userState.getMagnificationModeLocked(), + this::onMagnificationTransitionEndedLocked); + } + + /** + * Called when the magnification mode transition is completed. + */ + void onMagnificationTransitionEndedLocked(boolean success) { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + final int previousMode = userState.getMagnificationModeLocked() + ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; + if (!success && previousMode != 0) { + userState.setMagnificationModeLocked(previousMode); + persistMagnificationModeSettingLocked(previousMode); + } else { + mMainHandler.sendMessage(obtainMessage( + AccessibilityManagerService::notifyRefreshMagnificationModeToInputFilter, + this)); + } } private void notifyRefreshMagnificationModeToInputFilter() { @@ -2962,7 +2983,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub getWindowMagnificationMgr().setConnection(connection); } - WindowMagnificationManager getWindowMagnificationMgr() { + /** + * Getter of {@link WindowMagnificationManager}. + * + * @return WindowMagnificationManager + */ + public WindowMagnificationManager getWindowMagnificationMgr() { synchronized (mLock) { if (mWindowMagnificationMgr == null) { mWindowMagnificationMgr = new WindowMagnificationManager(mContext, mCurrentUserId); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java new file mode 100644 index 000000000000..af4b34f9613a --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java @@ -0,0 +1,237 @@ +/* + * 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.server.accessibility.magnification; + +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.provider.Settings; +import android.util.Slog; +import android.util.SparseArray; +import android.view.accessibility.MagnificationAnimationCallback; + +import com.android.server.accessibility.AccessibilityManagerService; + +/** + * Handles magnification mode transition. + */ +public class MagnificationTransitionController { + + private static final boolean DEBUG = false; + private static final String TAG = "MagnificationController"; + private final AccessibilityManagerService mAms; + private final PointF mTempPoint = new PointF(); + private final Object mLock; + private final SparseArray<DisableMagnificationCallback> + mMagnificationEndRunnableSparseArray = new SparseArray(); + + + /** + * A callback to inform the magnification transition result. + */ + public interface TransitionCallBack { + /** + * Invoked when the transition ends. + * @param success {@code true} if the transition success. + */ + void onResult(boolean success); + } + + public MagnificationTransitionController(AccessibilityManagerService ams, Object lock) { + mAms = ams; + mLock = lock; + } + + /** + * Transitions to the target Magnification mode with current center of the magnification mode + * if it is available. + * + * @param displayId The logical display + * @param targetMode The target magnification mode + * @param transitionCallBack The callback invoked when the transition is finished. + */ + public void transitionMagnificationModeLocked(int displayId, int targetMode, + @NonNull TransitionCallBack transitionCallBack) { + final PointF magnificationCenter = getCurrentMagnificationBoundsCenterLocked(displayId, + targetMode); + final DisableMagnificationCallback animationCallback = + getDisableMagnificationEndRunnableLocked(displayId); + if (magnificationCenter == null && animationCallback == null) { + transitionCallBack.onResult(true); + return; + } + + if (animationCallback != null) { + if (animationCallback.mCurrentMode == targetMode) { + animationCallback.restoreToCurrentMagnificationMode(); + return; + } + Slog.w(TAG, "request during transition, abandon current:" + + animationCallback.mTargetMode); + animationCallback.setExpiredAndRemoveFromListLocked(); + } + + if (magnificationCenter == null) { + Slog.w(TAG, "Invalid center, ignore it"); + transitionCallBack.onResult(true); + return; + } + final FullScreenMagnificationController screenMagnificationController = + getFullScreenMagnificationController(); + final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationManager(); + final float scale = windowMagnificationMgr.getPersistedScale(); + final DisableMagnificationCallback animationEndCallback = + new DisableMagnificationCallback(transitionCallBack, displayId, targetMode, + scale, magnificationCenter); + if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { + screenMagnificationController.reset(displayId, animationEndCallback); + } else { + windowMagnificationMgr.disableWindowMagnification(displayId, false, + animationEndCallback); + } + setDisableMagnificationCallbackLocked(displayId, animationEndCallback); + } + + private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked( + int displayId) { + return mMagnificationEndRunnableSparseArray.get(displayId); + } + + private void setDisableMagnificationCallbackLocked(int displayId, + @Nullable DisableMagnificationCallback callback) { + mMagnificationEndRunnableSparseArray.put(displayId, callback); + if (DEBUG) { + Slog.d(TAG, "setDisableMagnificationCallbackLocked displayId = " + displayId + + ", callback = " + callback); + } + } + + private FullScreenMagnificationController getFullScreenMagnificationController() { + return mAms.getFullScreenMagnificationController(); + } + + private WindowMagnificationManager getWindowMagnificationManager() { + return mAms.getWindowMagnificationMgr(); + } + + private @Nullable + PointF getCurrentMagnificationBoundsCenterLocked(int displayId, int targetMode) { + if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { + final WindowMagnificationManager magnificationManager = getWindowMagnificationManager(); + if (!magnificationManager.isWindowMagnifierEnabled(displayId)) { + return null; + } + mTempPoint.set(magnificationManager.getCenterX(displayId), + magnificationManager.getCenterY(displayId)); + } else { + final FullScreenMagnificationController screenMagnificationController = + getFullScreenMagnificationController(); + if (!screenMagnificationController.isMagnifying(displayId)) { + return null; + } + mTempPoint.set(screenMagnificationController.getCenterX(displayId), + screenMagnificationController.getCenterY(displayId)); + } + return mTempPoint; + } + + private final class DisableMagnificationCallback implements + MagnificationAnimationCallback { + private final TransitionCallBack mTransitionCallBack; + private boolean mExpired = false; + private final int mDisplayId; + private final int mTargetMode; + private final int mCurrentMode; + private final float mCurrentScale; + private final PointF mCurrentCenter = new PointF(); + + DisableMagnificationCallback(TransitionCallBack transitionCallBack, + int displayId, int targetMode, float scale, PointF currentCenter) { + mTransitionCallBack = transitionCallBack; + mDisplayId = displayId; + mTargetMode = targetMode; + mCurrentMode = mTargetMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; + mCurrentScale = scale; + mCurrentCenter.set(currentCenter); + } + + @Override + public void onResult(boolean success) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onResult success = " + success); + } + if (mExpired) { + return; + } + setExpiredAndRemoveFromListLocked(); + if (success) { + adjustCurrentCenterIfNeededLocked(); + applyMagnificationModeLocked(mTargetMode); + } + mTransitionCallBack.onResult(success); + } + } + + private void adjustCurrentCenterIfNeededLocked() { + if (mTargetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { + return; + } + final Region outRegion = new Region(); + getFullScreenMagnificationController().getMagnificationRegion(mDisplayId, outRegion); + if (outRegion.contains((int) mCurrentCenter.x, (int) mCurrentCenter.y)) { + return; + } + final Rect bounds = outRegion.getBounds(); + mCurrentCenter.set(bounds.exactCenterX(), bounds.exactCenterY()); + } + + void restoreToCurrentMagnificationMode() { + synchronized (mLock) { + if (mExpired) { + return; + } + setExpiredAndRemoveFromListLocked(); + applyMagnificationModeLocked(mCurrentMode); + mTransitionCallBack.onResult(true); + } + } + + void setExpiredAndRemoveFromListLocked() { + mExpired = true; + setDisableMagnificationCallbackLocked(mDisplayId, null); + } + + private void applyMagnificationModeLocked(int mode) { + if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { + getFullScreenMagnificationController().setScaleAndCenter(mDisplayId, + mCurrentScale, mCurrentCenter.x, + mCurrentCenter.y, true, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + } else { + getWindowMagnificationManager().enableWindowMagnification(mDisplayId, + mCurrentScale, mCurrentCenter.x, + mCurrentCenter.y); + } + } + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java index a401bcd3eabd..993027d1ca3c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java @@ -20,11 +20,12 @@ import static android.os.IBinder.DeathRecipient; import android.annotation.NonNull; import android.annotation.Nullable; -import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Slog; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.MagnificationAnimationCallback; /** * A wrapper of {@link IWindowMagnificationConnection}. @@ -50,9 +51,10 @@ class WindowMagnificationConnectionWrapper { } boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY, - @Nullable RemoteCallback endCallback) { + @Nullable MagnificationAnimationCallback callback) { try { - mConnection.enableWindowMagnification(displayId, scale, centerX, centerY, endCallback); + mConnection.enableWindowMagnification(displayId, scale, centerX, centerY, + transformToRemoteCallback(callback)); } catch (RemoteException e) { if (DBG) { Slog.e(TAG, "Error calling enableWindowMagnification()", e); @@ -74,9 +76,10 @@ class WindowMagnificationConnectionWrapper { return true; } - boolean disableWindowMagnification(int displayId, @Nullable RemoteCallback endCallback) { + boolean disableWindowMagnification(int displayId, + @Nullable MagnificationAnimationCallback callback) { try { - mConnection.disableWindowMagnification(displayId, endCallback); + mConnection.disableWindowMagnification(displayId, transformToRemoteCallback(callback)); } catch (RemoteException e) { if (DBG) { Slog.e(TAG, "Error calling disableWindowMagnification()", e); @@ -134,4 +137,27 @@ class WindowMagnificationConnectionWrapper { return true; } + private static @Nullable + IRemoteMagnificationAnimationCallback transformToRemoteCallback( + MagnificationAnimationCallback callback) { + if (callback == null) { + return null; + } + return new RemoteAnimationCallback(callback); + } + + private static class RemoteAnimationCallback extends + IRemoteMagnificationAnimationCallback.Stub { + + private final MagnificationAnimationCallback mCallback; + + RemoteAnimationCallback(@NonNull MagnificationAnimationCallback callback) { + mCallback = callback; + } + + @Override + public void onResult(boolean success) throws RemoteException { + mCallback.onResult(success); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index 9cbd78bf4482..c8e485f503ec 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -24,7 +24,6 @@ import android.content.IntentFilter; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; -import android.os.RemoteCallback; import android.os.RemoteException; import android.provider.Settings; import android.util.MathUtils; @@ -33,6 +32,7 @@ import android.util.SparseArray; import android.view.MotionEvent; import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.MagnificationAnimationCallback; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -91,7 +91,7 @@ public class WindowMagnificationManager implements */ public void setConnection(@Nullable IWindowMagnificationConnection connection) { synchronized (mLock) { - //Reset connectionWrapper. + // Reset connectionWrapper. if (mConnectionWrapper != null) { mConnectionWrapper.setConnectionCallback(null); if (mConnectionCallback != null) { @@ -241,11 +241,10 @@ public class WindowMagnificationManager implements * 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. - * @param endCallback Called when the animation is ended without any interruption or the - * window magnifier is disabled already. + * @param animationCallback Called when the animation result is valid. */ void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, - @Nullable Runnable endCallback) { + @Nullable MagnificationAnimationCallback animationCallback) { synchronized (mLock) { if (mConnectionWrapper == null) { return; @@ -254,7 +253,8 @@ public class WindowMagnificationManager implements if (magnifier == null) { magnifier = createWindowMagnifier(displayId); } - magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, endCallback); + magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, + animationCallback); } } @@ -273,16 +273,16 @@ public class WindowMagnificationManager implements * * @param displayId The logical display id. * @param clear {@true} Clears the state of window magnification. - * @param endCallback Called when the animation is ended without any interruption or the - * window magnifier is disabled already. + * @param animationCallback Called when the animation result is valid. */ - void disableWindowMagnification(int displayId, boolean clear, Runnable endCallback) { + void disableWindowMagnification(int displayId, boolean clear, + MagnificationAnimationCallback animationCallback) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null || mConnectionWrapper == null) { return; } - magnifier.disableWindowMagnificationInternal(endCallback); + magnifier.disableWindowMagnificationInternal(animationCallback); if (clear) { mWindowMagnifiers.delete(displayId); } @@ -312,7 +312,8 @@ public class WindowMagnificationManager implements * @param displayId The logical display id. * @return {@code true} if the window magnification is enabled. */ - boolean isWindowMagnifierEnabled(int displayId) { + @VisibleForTesting + public boolean isWindowMagnifierEnabled(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -408,6 +409,38 @@ public class WindowMagnificationManager implements } /** + * Returns the screen-relative X coordinate of the center of the magnified bounds. + * + * @param displayId The logical display id + * @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled. + */ + float getCenterX(int displayId) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return Float.NaN; + } + return magnifier.getCenterX(); + } + } + + /** + * Returns the screen-relative Y coordinate of the center of the magnified bounds. + * + * @param displayId The logical display id + * @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled. + */ + float getCenterY(int displayId) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return Float.NaN; + } + return magnifier.getCenterY(); + } + } + + /** * Creates the windowMagnifier based on the specified display and stores it. * * @param displayId logical display id. @@ -446,11 +479,13 @@ public class WindowMagnificationManager implements @Override public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { - WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); - if (magnifier == null) { - magnifier = createWindowMagnifier(displayId); + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + magnifier = createWindowMagnifier(displayId); + } + magnifier.onSourceBoundsChanged(sourceBounds); } - magnifier.onSourceBoundsChanged(sourceBounds); } @Override @@ -479,9 +514,9 @@ public class WindowMagnificationManager implements private boolean mEnabled; private final WindowMagnificationManager mWindowMagnificationManager; - //Records the bounds of window magnification. + // Records the bounds of window magnification. private final Rect mBounds = new Rect(); - //The magnified bounds on the screen. + // The magnified bounds on the screen. private final Rect mSourceBounds = new Rect(); WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) { @@ -491,22 +526,23 @@ public class WindowMagnificationManager implements @GuardedBy("mLock") void enableWindowMagnificationInternal(float scale, float centerX, float centerY, - @Nullable Runnable endCallback) { + @Nullable MagnificationAnimationCallback animationCallback) { if (mEnabled) { return; } final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale, - centerX, centerY, endCallback)) { + centerX, centerY, animationCallback)) { mScale = normScale; mEnabled = true; } } @GuardedBy("mLock") - void disableWindowMagnificationInternal(@Nullable Runnable endCallback) { + void disableWindowMagnificationInternal( + @Nullable MagnificationAnimationCallback animationResultCallback) { if (mEnabled && mWindowMagnificationManager.disableWindowMagnificationInternal( - mDisplayId, endCallback)) { + mDisplayId, animationResultCallback)) { mEnabled = false; } } @@ -562,26 +598,36 @@ public class WindowMagnificationManager implements mEnabled = false; } + @GuardedBy("mLock") public void onSourceBoundsChanged(Rect sourceBounds) { mSourceBounds.set(sourceBounds); } + + @GuardedBy("mLock") + float getCenterX() { + return mEnabled ? mSourceBounds.exactCenterX() : Float.NaN; + } + + @GuardedBy("mLock") + float getCenterY() { + return mEnabled ? mSourceBounds.exactCenterY() : Float.NaN; + } } private boolean enableWindowMagnificationInternal(int displayId, float scale, float centerX, - float centerY, Runnable endCallback) { + float centerY, MagnificationAnimationCallback animationCallback) { return mConnectionWrapper != null && mConnectionWrapper.enableWindowMagnification( - displayId, scale, centerX, centerY, - endCallback != null ? new RemoteCallback(bundle -> endCallback.run()) : null); + displayId, scale, centerX, centerY, animationCallback); } private boolean setScaleInternal(int displayId, float scale) { return mConnectionWrapper != null && mConnectionWrapper.setScale(displayId, scale); } - private boolean disableWindowMagnificationInternal(int displayId, Runnable endCallback) { + private boolean disableWindowMagnificationInternal(int displayId, + MagnificationAnimationCallback animationCallback) { return mConnectionWrapper != null && mConnectionWrapper.disableWindowMagnification( - displayId, - endCallback != null ? new RemoteCallback(bundle -> endCallback.run()) : null); + displayId, animationCallback); } private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 23a37c116c6d..b7355ce92c28 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -230,4 +230,19 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { verify(mMockWindowMagnificationMgr, never()).showMagnificationButton(anyInt(), anyInt()); } + + @SmallTest + public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() { + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); + userState.setMagnificationCapabilitiesLocked( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); + userState.setMagnificationModeLocked( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + mA11yms.onMagnificationTransitionEndedLocked(false); + + assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, + userState.getMagnificationModeLocked()); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java new file mode 100644 index 000000000000..cd8e39cfd2e7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java @@ -0,0 +1,279 @@ +/* + * 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.server.accessibility.magnification; + +import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.test.mock.MockContentResolver; +import android.view.Display; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; +import android.view.accessibility.MagnificationAnimationCallback; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.accessibility.AccessibilityManagerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for MagnificationController. + */ +@RunWith(AndroidJUnit4.class) +public class MagnificationTransitionControllerTest { + + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; + private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600); + private static final float MAGNIFIED_CENTER_X = 100; + private static final float MAGNIFIED_CENTER_Y = 200; + private static final float DEFAULT_SCALE = 3f; + private static final int CURRENT_USER_ID = UserHandle.USER_CURRENT; + private static final int MODE_WINDOW = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + private static final int MODE_FULLSCREEN = + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + + @Mock private AccessibilityManagerService mService; + @Mock private MagnificationTransitionController.TransitionCallBack mTransitionCallBack; + @Mock private Context mContext; + @Mock private FullScreenMagnificationController mScreenMagnificationController; + @Captor private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor; + + private MockWindowMagnificationConnection mMockConnection; + private WindowMagnificationManager mWindowMagnificationManager; + private MockContentResolver mMockResolver; + private MagnificationTransitionController mMagnificationTransitionController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + FakeSettingsProvider.clearSettingsProvider(); + mMockResolver = new MockContentResolver(); + mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContext.getContentResolver()).thenReturn(mMockResolver); + Settings.Secure.putFloatForUser(mMockResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE, + CURRENT_USER_ID); + mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID); + when(mService.getFullScreenMagnificationController()).thenReturn( + mScreenMagnificationController); + when(mService.getWindowMagnificationMgr()).thenReturn(mWindowMagnificationManager); + mMockConnection = new MockWindowMagnificationConnection(true); + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationTransitionController = new MagnificationTransitionController(mService, + new Object()); + } + + @After + public void tearDown() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @Test + public void transitionToWindowMode_notMagnifying_doNothing() throws RemoteException { + setMagnificationModeSettings(MODE_FULLSCREEN); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + verify(mTransitionCallBack).onResult(true); + verify(mScreenMagnificationController, never()).reset(anyInt(), + any(MagnificationAnimationCallback.class)); + verify(mMockConnection.getConnection(), never()).enableWindowMagnification(anyInt(), + anyFloat(), anyFloat(), anyFloat(), + nullable(IRemoteMagnificationAnimationCallback.class)); + } + + @Test + public void transitionToWindowMode_fullScreenMagnifying_disableFullScreenAndEnableWindow() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), + mCallbackArgumentCaptor.capture()); + mCallbackArgumentCaptor.getValue().onResult(true); + mMockConnection.invokeCallbacks(); + verify(mTransitionCallBack).onResult(true); + assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + } + + @Test + public void transitionToWindowMode_disablingWindowMode_enablingWindowWithFormerCenter() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + mMockConnection.invokeCallbacks(); + verify(mTransitionCallBack).onResult(true); + assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + } + + @Test + public void transitionToFullScreenMode_windowMagnifying_disableWindowAndEnableFullScreen() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + mMockConnection.invokeCallbacks(); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + true, MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(true); + } + + @Test + public void transitionToFullScreen_centerNotInTheBounds_magnifyTheCenterOfMagnificationBounds() + throws RemoteException { + final Rect magnificationBounds = MAGNIFICATION_REGION.getBounds(); + final PointF magnifiedCenter = new PointF(magnificationBounds.right + 100, + magnificationBounds.bottom + 100); + setMagnificationEnabled(MODE_WINDOW, magnifiedCenter.x, magnifiedCenter.y); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + mMockConnection.invokeCallbacks(); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, + magnificationBounds.exactCenterX(), magnificationBounds.exactCenterY(), true, + MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(true); + } + + @Test + public void transitionToFullScreenMode_disablingFullScreen_enableFullScreenWithFormerCenter() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + + verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + true, MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(true); + } + + + @Test + public void interruptDuringTransitionToFullScreenMode_windowMagnifying_notifyTransitionFailed() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + + // Enable window magnification while animating. + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, + Float.NaN, Float.NaN, null); + mMockConnection.invokeCallbacks(); + + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + true, MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(false); + } + + private void setMagnificationEnabled(int mode) throws RemoteException { + setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); + } + + private void setMagnificationEnabled(int mode, float centerX, float centerY) + throws RemoteException { + setMagnificationModeSettings(mode); + Mockito.reset(mScreenMagnificationController); + doAnswer(invocation -> { + final Region outRegion = invocation.getArgument(1); + outRegion.set(MAGNIFICATION_REGION); + return null; + }).when(mScreenMagnificationController).getMagnificationRegion(anyInt(), any(Region.class)); + + final boolean windowMagnifying = mWindowMagnificationManager.isWindowMagnifierEnabled( + TEST_DISPLAY); + if (windowMagnifying) { + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMockConnection.invokeCallbacks(); + } + if (mode == MODE_FULLSCREEN) { + when(mScreenMagnificationController.isMagnifying(TEST_DISPLAY)).thenReturn(true); + when(mScreenMagnificationController.getPersistedScale()).thenReturn(DEFAULT_SCALE); + when(mScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(DEFAULT_SCALE); + when(mScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn( + centerX); + when(mScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn( + centerY); + } else { + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, + centerX, centerY, null); + mMockConnection.invokeCallbacks(); + } + } + + private void setMagnificationModeSettings(int mode) { + Settings.Secure.putIntForUser(mMockResolver, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, mode, CURRENT_USER_ID); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java index f896d75ecb3b..2a5350454f67 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java @@ -25,12 +25,13 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; -import android.os.RemoteCallback; import android.os.RemoteException; import android.view.Display; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; @@ -38,16 +39,28 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback; * Mocks the basic logic of window magnification in System UI. We assume the screen size is * unlimited, so source bounds is always on the center of the mirror window bounds. */ -class MockWindowMagnificationConnection { +class MockWindowMagnificationConnection { public static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; private final IWindowMagnificationConnection mConnection; private final Binder mBinder; + private final boolean mSuspendCallback; + private boolean mHasPendingCallback = false; + private boolean mWindowMagnificationEnabled = false; private IBinder.DeathRecipient mDeathRecipient; private IWindowMagnificationConnectionCallback mIMirrorWindowCallback; + private Rect mMirrorWindowFrame = new Rect(0, 0, 500, 500); + private float mScale = 2.0f; + private Rect mSourceBounds = new Rect(); + private IRemoteMagnificationAnimationCallback mAnimationCallback; MockWindowMagnificationConnection() throws RemoteException { + this(false); + } + + MockWindowMagnificationConnection(boolean suspendCallback) throws RemoteException { + mSuspendCallback = suspendCallback; mConnection = mock(IWindowMagnificationConnection.class); mBinder = mock(Binder.class); when(mConnection.asBinder()).thenReturn(mBinder); @@ -66,33 +79,55 @@ class MockWindowMagnificationConnection { } private void stubConnection() throws RemoteException { + stubEnableWindowMagnification(); + stubDisableWindowMagnification(); + } + + private void stubEnableWindowMagnification() throws RemoteException { doAnswer((invocation) -> { final int displayId = invocation.getArgument(0); if (displayId != TEST_DISPLAY) { throw new IllegalArgumentException("only support default display :" + displayId); } + mWindowMagnificationEnabled = true; + final float scale = invocation.getArgument(1); + mScale = Float.isNaN(scale) ? mScale : scale; computeMirrorWindowFrame(invocation.getArgument(2), invocation.getArgument(3)); - final RemoteCallback callback = invocation.getArgument(4); - if (callback != null) { - callback.sendResult(null); + setAnimationCallback(invocation.getArgument(4)); + computeSourceBounds(); + mHasPendingCallback = true; + if (!mSuspendCallback) { + invokeCallbacksInternal(true); } - mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY, - mMirrorWindowFrame); return null; - }).when(mConnection).enableWindowMagnification(anyInt(), - anyFloat(), anyFloat(), anyFloat(), nullable(RemoteCallback.class)); + }).when(mConnection).enableWindowMagnification(anyInt(), anyFloat(), anyFloat(), anyFloat(), + nullable(IRemoteMagnificationAnimationCallback.class)); + } + private void stubDisableWindowMagnification() throws RemoteException { doAnswer((invocation) -> { final int displayId = invocation.getArgument(0); if (displayId != TEST_DISPLAY) { throw new IllegalArgumentException("only support default display :" + displayId); } - final RemoteCallback callback = invocation.getArgument(1); - if (callback != null) { - callback.sendResult(null); + setAnimationCallback(invocation.getArgument(1)); + mHasPendingCallback = true; + if (!mSuspendCallback) { + invokeCallbacksInternal(true); } return null; - }).when(mConnection).disableWindowMagnification(anyInt(), nullable(RemoteCallback.class)); + }).when(mConnection).disableWindowMagnification(anyInt(), + nullable(IRemoteMagnificationAnimationCallback.class)); + } + + private void computeSourceBounds() { + final int halfWidth = mMirrorWindowFrame.width() / 2; + final int halfHeight = mMirrorWindowFrame.height() / 2; + final int left = mMirrorWindowFrame.left + (halfWidth - (int) (halfWidth / mScale)); + final int right = mMirrorWindowFrame.right - (halfWidth - (int) (halfWidth / mScale)); + final int top = mMirrorWindowFrame.top + (halfHeight - (int) (halfHeight / mScale)); + final int bottom = mMirrorWindowFrame.bottom - (halfHeight - (int) (halfHeight / mScale)); + mSourceBounds.set(left, top, right, bottom); } private void computeMirrorWindowFrame(float centerX, float centerY) { @@ -103,6 +138,13 @@ class MockWindowMagnificationConnection { mMirrorWindowFrame.offset((int) offsetX, (int) offsetY); } + private void sendAnimationEndCallbackIfNeeded(boolean success) throws RemoteException { + if (mAnimationCallback != null) { + mAnimationCallback.onResult(success); + mAnimationCallback = null; + } + } + IWindowMagnificationConnection getConnection() { return mConnection; } @@ -119,8 +161,38 @@ class MockWindowMagnificationConnection { return mIMirrorWindowCallback; } - public Rect getMirrorWindowFrame() { + Rect getMirrorWindowFrame() { return new Rect(mMirrorWindowFrame); } + + void invokeCallbacks() throws RemoteException { + if (!mSuspendCallback) { + throw new IllegalStateException("Invoke callbacks automatically"); + } + invokeCallbacksInternal(true); + } + + private void invokeCallbacksInternal(boolean success) throws RemoteException { + if (!mHasPendingCallback) { + throw new IllegalStateException("There is no any pending callbacks"); + } + if (mWindowMagnificationEnabled && mIMirrorWindowCallback != null) { + mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY, + mMirrorWindowFrame); + mIMirrorWindowCallback.onSourceBoundsChanged(TEST_DISPLAY, + mSourceBounds); + } + sendAnimationEndCallbackIfNeeded(success); + mHasPendingCallback = false; + } + + private void setAnimationCallback( + @Nullable IRemoteMagnificationAnimationCallback animationCallback) + throws RemoteException { + if (mAnimationCallback != null) { + invokeCallbacksInternal(false); + } + mAnimationCallback = animationCallback; + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java index 9ef65d9cce09..c88bc3b2e15b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java @@ -17,14 +17,17 @@ package com.android.server.accessibility.magnification; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import android.os.RemoteCallback; import android.os.RemoteException; import android.provider.Settings; import android.view.Display; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.MagnificationAnimationCallback; import org.junit.Before; import org.junit.Test; @@ -40,26 +43,29 @@ public class WindowMagnificationConnectionWrapperTest { private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; - @Mock private IWindowMagnificationConnection mConnection; @Mock private IWindowMagnificationConnectionCallback mCallback; @Mock - private RemoteCallback.OnResultListener mOnResultListener; - private RemoteCallback mRemoteCallback; + private MagnificationAnimationCallback mAnimationCallback; + + private MockWindowMagnificationConnection mMockWindowMagnificationConnection; private WindowMagnificationConnectionWrapper mConnectionWrapper; @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); + mMockWindowMagnificationConnection = new MockWindowMagnificationConnection(); + mConnection = mMockWindowMagnificationConnection.getConnection(); mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection); - mRemoteCallback = new RemoteCallback(mOnResultListener); } @Test public void enableWindowMagnification() throws RemoteException { - mConnectionWrapper.enableWindowMagnification(TEST_DISPLAY, 2, 100f, 200f, mRemoteCallback); - verify(mConnection).enableWindowMagnification(TEST_DISPLAY, 2, 100f, 200f, mRemoteCallback); + mConnectionWrapper.enableWindowMagnification(TEST_DISPLAY, 2, 100f, 200f, + mAnimationCallback); + + verify(mAnimationCallback).onResult(true); } @Test @@ -70,8 +76,11 @@ public class WindowMagnificationConnectionWrapperTest { @Test public void disableWindowMagnification() throws RemoteException { - mConnectionWrapper.disableWindowMagnification(TEST_DISPLAY, mRemoteCallback); - verify(mConnection).disableWindowMagnification(TEST_DISPLAY, mRemoteCallback); + mConnectionWrapper.disableWindowMagnification(TEST_DISPLAY, mAnimationCallback); + + verify(mConnection).disableWindowMagnification(eq(TEST_DISPLAY), + any(IRemoteMagnificationAnimationCallback.class)); + verify(mAnimationCallback).onResult(true); } @Test @@ -99,5 +108,4 @@ public class WindowMagnificationConnectionWrapperTest { mConnectionWrapper.setConnectionCallback(mCallback); verify(mConnection).setConnectionCallback(mCallback); } - } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index dcb1262ad2de..89b0a03a25bb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -47,7 +47,9 @@ import android.test.mock.MockContentResolver; import android.view.Display; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.MagnificationAnimationCallback; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; @@ -73,7 +75,7 @@ public class WindowMagnificationManagerTest { @Mock private StatusBarManagerInternal mMockStatusBarManagerInternal; @Mock - private Runnable mEndCallback; + private MagnificationAnimationCallback mAnimationCallback; private MockContentResolver mResolver; private WindowMagnificationManager mWindowMagnificationManager; @@ -177,9 +179,11 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f, - mEndCallback); + mAnimationCallback); - verify(mEndCallback).run(); + verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f), + eq(200f), eq(300f), any(IRemoteMagnificationAnimationCallback.class)); + verify(mAnimationCallback).onResult(true); } @Test @@ -194,13 +198,17 @@ public class WindowMagnificationManagerTest { } @Test - public void disableWithCallback_hasConnectionAndEnabled_disableWindowMagnification() { + public void disableWithCallback_hasConnectionAndEnabled_disableWindowMagnification() + throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false, mEndCallback); + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false, + mAnimationCallback); - verify(mEndCallback).run(); + verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY), + any(IRemoteMagnificationAnimationCallback.class)); + verify(mAnimationCallback).onResult(true); } @Test @@ -352,6 +360,15 @@ public class WindowMagnificationManagerTest { assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } + @Test + public void centerGetter_enabledOnTestDisplay_expectedValues() { + mWindowMagnificationManager.requestConnection(true); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); + + assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f); + assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); + } + private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) { final int len = pointersLocation.length; |