diff options
13 files changed, 477 insertions, 47 deletions
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl index 67d96678f420..62d029bd1be6 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl +++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl @@ -32,7 +32,7 @@ oneway interface IWindowMagnificationConnection { /** * Enables window magnification on specified display with given center and scale and animation. * - * @param displayId The logical display id. + * @param displayId the logical display id. * @param scale magnification scale. * @param centerX the screen-relative X coordinate around which to center, * or {@link Float#NaN} to leave unchanged. @@ -51,7 +51,7 @@ oneway interface IWindowMagnificationConnection { /** * Sets the scale of the window magnifier on specified display. * - * @param displayId The logical display id. + * @param displayId the logical display id. * @param scale magnification scale. */ void setScale(int displayId, float scale); @@ -59,7 +59,7 @@ oneway interface IWindowMagnificationConnection { /** * Disables window magnification on specified display with animation. * - * @param displayId The logical display id. + * @param displayId the logical display id. * @param callback The callback called when the animation is completed or interrupted. */ void disableWindowMagnification(int displayId, @@ -68,6 +68,7 @@ oneway interface IWindowMagnificationConnection { /** * Moves the window magnifier on the specified display. It has no effect while animating. * + * @param displayId the logical display id. * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in * current screen pixels. * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in @@ -76,9 +77,20 @@ oneway interface IWindowMagnificationConnection { void moveWindowMagnifier(int displayId, float offsetX, float offsetY); /** + * Moves the window magnifier on the given display. + * + * @param displayId the logical display id. + * @param positionX the x-axis position of the center of the magnified source bounds. + * @param positionY the y-axis position of the center of the magnified source bounds. + * @param callback the callback called when the animation is completed or interrupted. + */ + void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY, + in IRemoteMagnificationAnimationCallback callback); + + /** * Requests System UI show magnification mode button UI on the specified display. * - * @param displayId The logical display id. + * @param displayId the logical display id. * @param magnificationMode the current magnification mode. */ void showMagnificationButton(int displayId, int magnificationMode); @@ -86,7 +98,7 @@ oneway interface IWindowMagnificationConnection { /** * Requests System UI remove magnification mode button UI on the specified display. * - * @param displayId The logical display id. + * @param displayId the logical display id. */ void removeMagnificationButton(int displayId); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 885a1777f30b..9b7a8f8d915c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -172,6 +172,17 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie } @MainThread + void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY, + IRemoteMagnificationAnimationCallback callback) { + final WindowMagnificationController windowMagnificationController = + mMagnificationControllerSupplier.get(displayId); + if (windowMagnificationController != null) { + windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY, + callback); + } + } + + @MainThread void disableWindowMagnification(int displayId, @Nullable IRemoteMagnificationAnimationCallback callback) { final WindowMagnificationController windowMagnificationController = diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java index dc1e0054ff24..3b4114bfb984 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java @@ -156,6 +156,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp } mAnimationCallback = animationCallback; setupEnableAnimationSpecs(scale, centerX, centerY); + if (mEndSpec.equals(mStartSpec)) { if (mState == STATE_DISABLED) { mController.enableWindowMagnificationInternal(scale, centerX, centerY, @@ -178,6 +179,24 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp } } + void moveWindowMagnifierToPosition(float centerX, float centerY, + IRemoteMagnificationAnimationCallback callback) { + if (mState == STATE_ENABLED) { + // We set the animation duration to shortAnimTime which would be reset at the end. + mValueAnimator.setDuration(mContext.getResources() + .getInteger(com.android.internal.R.integer.config_shortAnimTime)); + enableWindowMagnification(Float.NaN, centerX, centerY, + /* magnificationFrameOffsetRatioX */ Float.NaN, + /* magnificationFrameOffsetRatioY */ Float.NaN, callback); + } else if (mState == STATE_ENABLING) { + sendAnimationCallback(false); + mAnimationCallback = callback; + mValueAnimator.setDuration(mContext.getResources() + .getInteger(com.android.internal.R.integer.config_shortAnimTime)); + setupEnableAnimationSpecs(Float.NaN, centerX, centerY); + } + } + private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) { if (mController == null) { return; @@ -193,9 +212,16 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp R.integer.magnification_default_scale) : scale, centerX, centerY); } else { mStartSpec.set(currentScale, currentCenterX, currentCenterY); - mEndSpec.set(Float.isNaN(scale) ? currentScale : scale, - Float.isNaN(centerX) ? currentCenterX : centerX, - Float.isNaN(centerY) ? currentCenterY : centerY); + + final float endScale = (mState == STATE_ENABLING ? mEndSpec.mScale : currentScale); + final float endCenterX = + (mState == STATE_ENABLING ? mEndSpec.mCenterX : currentCenterX); + final float endCenterY = + (mState == STATE_ENABLING ? mEndSpec.mCenterY : currentCenterY); + + mEndSpec.set(Float.isNaN(scale) ? endScale : scale, + Float.isNaN(centerX) ? endCenterX : centerX, + Float.isNaN(centerY) ? endCenterY : centerY); } if (DEBUG) { Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = " @@ -269,6 +295,9 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp setState(STATE_ENABLED); } sendAnimationCallback(true); + // We reset the duration to config_longAnimTime + mValueAnimator.setDuration(mContext.getResources() + .getInteger(com.android.internal.R.integer.config_longAnimTime)); } @Override @@ -313,10 +342,10 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY); } - private static ValueAnimator newValueAnimator(Resources resources) { + private static ValueAnimator newValueAnimator(Resources resource) { final ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setDuration( - resources.getInteger(com.android.internal.R.integer.config_longAnimTime)); + resource.getInteger(com.android.internal.R.integer.config_longAnimTime)); valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f)); valueAnimator.setFloatValues(0.0f, 1.0f); return valueAnimator; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java index 1d22633455e9..0522d43e99fb 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java @@ -77,6 +77,13 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S } @Override + public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY, + IRemoteMagnificationAnimationCallback callback) { + mHandler.post(() -> mWindowMagnification.moveWindowMagnifierToPositionInternal( + displayId, positionX, positionY, callback)); + } + + @Override public void showMagnificationButton(int displayId, int magnificationMode) { mHandler.post( () -> mModeSwitchesController.showButton(displayId, magnificationMode)); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index de03993a6f17..e109b5c9e519 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -985,6 +985,14 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } } + void moveWindowMagnifierToPosition(float positionX, float positionY, + IRemoteMagnificationAnimationCallback callback) { + if (mMirrorSurfaceView == null) { + return; + } + mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback); + } + /** * Gets the scale. * 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 796af115bf68..58b4af43a9b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java @@ -134,6 +134,16 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { } @Test + public void moveWindowMagnifierToPosition() throws RemoteException { + mIWindowMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY, + 100f, 200f, mAnimationCallback); + waitForIdleSync(); + + verify(mWindowMagnificationController).moveWindowMagnifierToPosition( + eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class)); + } + + @Test public void showMagnificationButton() throws RemoteException { mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java new file mode 100644 index 000000000000..30bff0943da7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import android.os.RemoteException; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub { + + private final CountDownLatch mCountDownLatch; + private final AtomicInteger mSuccessCount; + private final AtomicInteger mFailedCount; + + MockMagnificationAnimationCallback(CountDownLatch countDownLatch) { + mCountDownLatch = countDownLatch; + mSuccessCount = new AtomicInteger(); + mFailedCount = new AtomicInteger(); + } + + public int getSuccessCount() { + return mSuccessCount.get(); + } + + public int getFailedCount() { + return mFailedCount.get(); + } + + @Override + public void onResult(boolean success) throws RemoteException { + mCountDownLatch.countDown(); + if (success) { + mSuccessCount.getAndIncrement(); + } else { + mFailedCount.getAndIncrement(); + } + } +} 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 3cc177dd8d91..21c3d6ea0660 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -60,6 +60,8 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @Ignore @@ -218,6 +220,29 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } @Test + public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault() + throws InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(2); + final MockMagnificationAnimationCallback animationCallback = + new MockMagnificationAnimationCallback(countDownLatch); + + enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, + animationCallback); + mInstrumentation.runOnMainSync( + () -> { + mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, + Float.NaN, Float.NaN, animationCallback); + }); + + assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); + // The callback in 2nd enableWindowMagnification will return true + assertEquals(1, animationCallback.getSuccessCount()); + // The callback in 1st enableWindowMagnification will return false + assertEquals(1, animationCallback.getFailedCount()); + verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); + } + + @Test public void enableWindowMagnificationWithScaleOne_enabled_AnimationAndInvokeCallback() throws RemoteException { enableWindowMagnificationWithoutAnimation(); @@ -425,6 +450,102 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } @Test + public void moveWindowMagnifierToPosition_enabled_expectedValues() + throws InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final MockMagnificationAnimationCallback animationCallback = + new MockMagnificationAnimationCallback(countDownLatch); + final float targetCenterX = DEFAULT_CENTER_X + 100; + final float targetCenterY = DEFAULT_CENTER_Y + 100; + enableWindowMagnificationWithoutAnimation(); + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + targetCenterX, targetCenterY, animationCallback); + }); + + assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); + assertEquals(1, animationCallback.getSuccessCount()); + assertEquals(0, animationCallback.getFailedCount()); + verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); + } + + @Test + public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne() + throws InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(4); + final MockMagnificationAnimationCallback animationCallback = + new MockMagnificationAnimationCallback(countDownLatch); + enableWindowMagnificationWithoutAnimation(); + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback); + }); + + assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); + // only the last one callback will return true + assertEquals(1, animationCallback.getSuccessCount()); + // the others will return false + assertEquals(3, animationCallback.getFailedCount()); + verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40); + } + + @Test + public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne() + throws InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(2); + final MockMagnificationAnimationCallback animationCallback = + new MockMagnificationAnimationCallback(countDownLatch); + final float targetCenterX = DEFAULT_CENTER_X + 100; + final float targetCenterY = DEFAULT_CENTER_Y + 100; + + enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, + animationCallback); + mInstrumentation.runOnMainSync( + () -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + targetCenterX, targetCenterY, animationCallback); + }); + + assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); + // The callback in moveWindowMagnifierToPosition will return true + assertEquals(1, animationCallback.getSuccessCount()); + // The callback in enableWindowMagnification will return false + assertEquals(1, animationCallback.getFailedCount()); + verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); + } + + @Test + public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault() + throws InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(2); + final MockMagnificationAnimationCallback animationCallback = + new MockMagnificationAnimationCallback(countDownLatch); + + enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod, + animationCallback); + mInstrumentation.runOnMainSync( + () -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + Float.NaN, Float.NaN, animationCallback); + }); + + assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); + // The callback in moveWindowMagnifierToPosition will return true + assertEquals(1, animationCallback.getSuccessCount()); + // The callback in enableWindowMagnification will return false + assertEquals(1, animationCallback.getFailedCount()); + verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); + } + + @Test public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); @@ -569,6 +690,20 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f); } + @Test + public void moveWindowMagnifierToPosition_enabled() { + final float targetCenterX = DEFAULT_CENTER_X + 100; + final float targetCenterY = DEFAULT_CENTER_Y + 100; + enableWindowMagnificationWithoutAnimation(); + + mInstrumentation.runOnMainSync( + () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY, + mAnimationCallback)); + SystemClock.sleep(mWaitingAnimationPeriod); + + verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); + } + private void verifyFinalSpec(float expectedScale, float expectedCenterX, float expectedCenterY) { assertEquals(expectedScale, mController.getScale(), 0f); @@ -663,6 +798,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } @Override + void moveWindowMagnifierToPosition(float positionX, float positionY, + IRemoteMagnificationAnimationCallback callback) { + super.moveWindowMagnifierToPosition(positionX, positionY, callback); + mSpyController.moveWindowMagnifierToPosition(positionX, positionY, callback); + } + + @Override void setScale(float scale) { super.setScale(scale); mSpyController.setScale(scale); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 6e5926db519d..19efd11d3a00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -88,6 +88,8 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @LargeTest @@ -96,12 +98,16 @@ import java.util.concurrent.atomic.AtomicInteger; public class WindowMagnificationControllerTest extends SysuiTestCase { private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; + private static final long ANIMATION_DURATION_MS = 300; + private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS; @Mock private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; @Mock private MirrorWindowControl mMirrorWindowControl; @Mock private WindowMagnifierCallback mWindowMagnifierCallback; + @Mock + IRemoteMagnificationAnimationCallback mAnimationCallback; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); @@ -287,6 +293,82 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test + public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback() + throws InterruptedException { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + Float.NaN, 0, 0, null); + }); + final CountDownLatch countDownLatch = new CountDownLatch(1); + final MockMagnificationAnimationCallback animationCallback = + new MockMagnificationAnimationCallback(countDownLatch); + final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); + verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) + .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); + final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10; + final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10; + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.moveWindowMagnifierToPosition( + targetCenterX, targetCenterY, animationCallback); + }); + + assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); + assertEquals(1, animationCallback.getSuccessCount()); + assertEquals(0, animationCallback.getFailedCount()); + verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) + .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); + assertEquals(mWindowMagnificationController.getCenterX(), + sourceBoundsCaptor.getValue().exactCenterX(), 0); + assertEquals(mWindowMagnificationController.getCenterY(), + sourceBoundsCaptor.getValue().exactCenterY(), 0); + assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0); + assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0); + } + + @Test + public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback() + throws InterruptedException { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + Float.NaN, 0, 0, null); + }); + final CountDownLatch countDownLatch = new CountDownLatch(4); + final MockMagnificationAnimationCallback animationCallback = + new MockMagnificationAnimationCallback(countDownLatch); + final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); + verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) + .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); + final float centerX = sourceBoundsCaptor.getValue().exactCenterX(); + final float centerY = sourceBoundsCaptor.getValue().exactCenterY(); + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.moveWindowMagnifierToPosition( + centerX + 10, centerY + 10, animationCallback); + mWindowMagnificationController.moveWindowMagnifierToPosition( + centerX + 20, centerY + 20, animationCallback); + mWindowMagnificationController.moveWindowMagnifierToPosition( + centerX + 30, centerY + 30, animationCallback); + mWindowMagnificationController.moveWindowMagnifierToPosition( + centerX + 40, centerY + 40, animationCallback); + }); + + assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); + // only the last one callback will return true + assertEquals(1, animationCallback.getSuccessCount()); + // the others will return false + assertEquals(3, animationCallback.getFailedCount()); + verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) + .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); + assertEquals(mWindowMagnificationController.getCenterX(), + sourceBoundsCaptor.getValue().exactCenterX(), 0); + assertEquals(mWindowMagnificationController.getCenterY(), + sourceBoundsCaptor.getValue().exactCenterY(), 0); + assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0); + assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0); + } + + @Test public void setScale_enabled_expectedValueAndUpdateStateDescription() { mInstrumentation.runOnMainSync( () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f, 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 25dcc2aea41b..041eece5ce48 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java @@ -133,6 +133,25 @@ class WindowMagnificationConnectionWrapper { return true; } + boolean moveWindowMagnifierToPosition(int displayId, float positionX, float positionY, + @Nullable MagnificationAnimationCallback callback) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".moveWindowMagnifierToPosition", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId + + ";positionX=" + positionX + ";positionY=" + positionY); + } + try { + mConnection.moveWindowMagnifierToPosition(displayId, positionX, positionY, + transformToRemoteCallback(callback, mTrace)); + } catch (RemoteException e) { + if (DBG) { + Slog.e(TAG, "Error calling moveWindowMagnifierToPosition()", e); + } + return false; + } + return true; + } + boolean showMagnificationButton(int displayId, int magnificationMode) { if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".showMagnificationButton", 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 0844d126f763..fadb07ef4e11 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -383,9 +383,12 @@ public class WindowMagnificationManager implements float toCenterX = (float) (left + right) / 2; float toCenterY = (float) (top + bottom) / 2; - if (!isPositionInSourceBounds(displayId, toCenterX, toCenterY) - && isTrackingTypingFocusEnabled(displayId)) { - enableWindowMagnification(displayId, Float.NaN, toCenterX, toCenterY); + synchronized (mLock) { + if (!isPositionInSourceBounds(displayId, toCenterX, toCenterY) + && isTrackingTypingFocusEnabled(displayId)) { + moveWindowMagnifierToPositionInternal(displayId, toCenterX, toCenterY, + STUB_ANIMATION_CALLBACK); + } } } @@ -426,7 +429,7 @@ public class WindowMagnificationManager implements * @param displayId The logical display id. * @param trackingTypingFocusEnabled Enabled or disable the function of tracking typing focus. */ - private void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) { + void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -569,8 +572,11 @@ public class WindowMagnificationManager implements animationCallback, windowPosition, id); } - if (enabled && !previousEnabled) { - mCallback.onWindowMagnificationActivationState(displayId, true); + if (enabled) { + setTrackingTypingFocusEnabled(displayId, true); + if (!previousEnabled) { + mCallback.onWindowMagnificationActivationState(displayId, true); + } } return enabled; } @@ -632,14 +638,13 @@ public class WindowMagnificationManager implements } } + @GuardedBy("mLock") boolean isPositionInSourceBounds(int displayId, float x, float y) { - synchronized (mLock) { - WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); - if (magnifier == null) { - return false; - } - return magnifier.isPositionInSourceBounds(x, y); + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return false; } + return magnifier.isPositionInSourceBounds(x, y); } /** @@ -954,7 +959,6 @@ public class WindowMagnificationManager implements mWindowMagnificationManager = windowMagnificationManager; } - @GuardedBy("mLock") boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback, @WindowPosition int windowPosition, int id) { @@ -1032,7 +1036,6 @@ public class WindowMagnificationManager implements return mIdOfLastServiceToControl; } - @GuardedBy("mLock") int pointersInWindow(MotionEvent motionEvent) { int count = 0; final int pointerCount = motionEvent.getPointerCount(); @@ -1131,8 +1134,16 @@ public class WindowMagnificationManager implements displayId, animationCallback); } + @GuardedBy("mLock") private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) { return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifier( displayId, offsetX, offsetY); } + + @GuardedBy("mLock") + private boolean moveWindowMagnifierToPositionInternal(int displayId, float positionX, + float positionY, MagnificationAnimationCallback animationCallback) { + return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifierToPosition( + displayId, positionX, positionY, 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 3822dc362b6b..4b77764c41e5 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 @@ -94,6 +94,14 @@ public class WindowMagnificationConnectionWrapperTest { } @Test + public void moveWindowMagnifierToPosition() throws RemoteException { + mConnectionWrapper.moveWindowMagnifierToPosition(TEST_DISPLAY, 100, 150, + mAnimationCallback); + verify(mConnection).moveWindowMagnifierToPosition(eq(TEST_DISPLAY), + eq(100f), eq(150f), any(IRemoteMagnificationAnimationCallback.class)); + } + + @Test public void showMagnificationButton() throws RemoteException { mConnectionWrapper.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); 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 68595c54f606..c05d8c602839 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 @@ -21,6 +21,8 @@ import static com.android.server.accessibility.magnification.MockWindowMagnifica import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.doAnswer; @@ -291,7 +293,7 @@ public class WindowMagnificationManagerTest { } @Test - public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnification() + public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnifier() throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); @@ -304,14 +306,13 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); - verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY), - eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), - eq(0f), eq(0f), notNull()); + verify(mMockConnection.getConnection(), never()) + .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any()); } @Test - public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnification() + public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnifier() throws RemoteException { final float distanceX = 10f; final float distanceY = 10f; @@ -326,13 +327,12 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); - verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY), - eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), - eq(0f), eq(0f), notNull()); + verify(mMockConnection.getConnection(), never()) + .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any()); } @Test - public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnification() + public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnifier() throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); @@ -344,12 +344,12 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); - verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY), - eq(3f), eq(500f), eq(500f), eq(0f), eq(0f), notNull()); + verify(mMockConnection.getConnection(), never()) + .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any()); } @Test - public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnification() + public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnifier() throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); @@ -361,18 +361,16 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); - verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f), + verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), - eq(0f), eq(0f), notNull()); + any(IRemoteMagnificationAnimationCallback.class)); } @Test - public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnification() + public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnifier() throws RemoteException { - final PointF initialPoint = new PointF(50f, 50f); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, - initialPoint.x, initialPoint.y); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY); mWindowMagnificationManager.onImeWindowVisibilityChanged(true); final Region outRegion = new Region(); @@ -383,14 +381,13 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); - verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), - eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), - eq(0f), eq(0f), notNull()); + verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY), + eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), + any(IRemoteMagnificationAnimationCallback.class)); } @Test - public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnification() - throws RemoteException { + public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnifier() { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); final Region beforeRegion = new Region(); @@ -408,6 +405,46 @@ public class WindowMagnificationManagerTest { } @Test + public void onRectangleOnScreenRequested_trackingDisabled_withoutMovingMagnifier() { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false); + final Region beforeRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); + final Rect requestedRect = beforeRegion.getBounds(); + requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + final Region afterRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion); + assertEquals(afterRegion, beforeRegion); + } + + @Test + public void onRectangleOnScreenRequested_trackingDisabledAndEnabledMagnifier_movingMagnifier() + throws RemoteException { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false); + final Region beforeRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); + final Rect requestedRect = beforeRegion.getBounds(); + requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + // Enabling a window magnifier again will turn on the tracking typing focus functionality. + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY), + eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), + any(IRemoteMagnificationAnimationCallback.class)); + } + + @Test public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN); |