Loading services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +2 −1 Original line number Diff line number Diff line Loading @@ -287,7 +287,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public static final int INVALID_SERVICE_ID = -1; // Each service has an ID. Also provide one for magnification gesture handling // Each service has an ID. Also provide one for magnification gesture handling. // This ID is also used for mouse event handling. public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0; private static int sIdCounter = MAGNIFICATION_GESTURE_HANDLER_ID + 1; Loading services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +53 −3 Original line number Diff line number Diff line Loading @@ -121,6 +121,8 @@ public class FullScreenMagnificationController implements @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier; @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier; private boolean mIsPointerMotionFilterInstalled = false; /** * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds * magnification information per display. Loading Loading @@ -830,9 +832,17 @@ public class FullScreenMagnificationController implements return; } final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { setOffset(mCurrentMagnificationSpec.offsetX - offsetX, mCurrentMagnificationSpec.offsetY - offsetY, id); } @GuardedBy("mLock") void setOffset(float offsetX, float offsetY, int id) { if (!mRegistered) { return; } if (updateCurrentSpecWithOffsetsLocked(offsetX, offsetY)) { onMagnificationChangedLocked(/* isScaleTransient= */ false); } if (id != INVALID_SERVICE_ID) { Loading Loading @@ -1065,6 +1075,7 @@ public class FullScreenMagnificationController implements if (display.register()) { mDisplays.put(displayId, display); mScreenStateObserver.registerIfNecessary(); configurePointerMotionFilter(true); } } } Loading Loading @@ -1612,6 +1623,28 @@ public class FullScreenMagnificationController implements } } /** * Sets the offset of the magnified region. * * @param displayId The logical display id. * @param offsetX the offset of the magnified region in the X coordinate, in current * screen pixels. * @param offsetY the offset of the magnified region in the Y coordinate, in current * screen pixels. * @param id the ID of the service requesting the change */ @SuppressWarnings("GuardedBy") // errorprone cannot recognize an inner class guarded by an outer class member. public void setOffset(int displayId, float offsetX, float offsetY, int id) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return; } display.setOffset(offsetX, offsetY, id); } } /** * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the * opposite direction as the offsets passed in here. Loading Loading @@ -1885,6 +1918,7 @@ public class FullScreenMagnificationController implements } if (!hasRegister) { mScreenStateObserver.unregister(); configurePointerMotionFilter(false); } } Loading @@ -1900,6 +1934,22 @@ public class FullScreenMagnificationController implements } } private void configurePointerMotionFilter(boolean enabled) { if (!Flags.enableMagnificationFollowsMouseWithPointerMotionFilter()) { return; } if (enabled == mIsPointerMotionFilterInstalled) { return; } if (!enabled) { mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter(null); } else { mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter( new FullScreenMagnificationPointerMotionEventFilter(this)); } mIsPointerMotionFilterInstalled = enabled; } private boolean traceEnabled() { return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( FLAGS_WINDOW_MANAGER_INTERNAL); Loading services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +7 −2 Original line number Diff line number Diff line Loading @@ -182,6 +182,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final int mMinimumVelocity; private final int mMaximumVelocity; @Nullable private final MouseEventHandler mMouseEventHandler; public FullScreenMagnificationGestureHandler( Loading Loading @@ -313,7 +314,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize( R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop); mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); mMouseEventHandler = new MouseEventHandler(mFullScreenMagnificationController); mMouseEventHandler = Flags.enableMagnificationFollowsMouseWithPointerMotionFilter() ? null : new MouseEventHandler(mFullScreenMagnificationController); if (mDetectShortcutTrigger) { mScreenStateReceiver = new ScreenStateReceiver(context, this); Loading @@ -337,9 +340,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @Override void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (!mFullScreenMagnificationController.isActivated(mDisplayId)) { if (mMouseEventHandler == null || !mFullScreenMagnificationController.isActivated(mDisplayId)) { return; } // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are // over, rather than only interacting with the current display. Loading services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.annotation.NonNull; import com.android.server.input.InputManagerInternal; /** * Handles pointer motion event for full screen magnification. * Responsible for controlling magnification's cursor following feature. */ public class FullScreenMagnificationPointerMotionEventFilter implements InputManagerInternal.AccessibilityPointerMotionFilter { private final FullScreenMagnificationController mController; public FullScreenMagnificationPointerMotionEventFilter( FullScreenMagnificationController controller) { mController = controller; } /** * This call happens on the input hot path and it is extremely performance sensitive. It * also must not call back into native code. */ @Override @NonNull public float[] filterPointerMotionEvent(float dx, float dy, float currentX, float currentY, int displayId) { if (!mController.isActivated(displayId)) { // unrelated display. return new float[]{dx, dy}; } // TODO(361817142): implement centered and edge following types. // Continuous cursor following. float scale = mController.getScale(displayId); final float newCursorX = currentX + dx; final float newCursorY = currentY + dy; mController.setOffset(displayId, newCursorX - newCursorX * scale, newCursorY - newCursorY * scale, MAGNIFICATION_GESTURE_HANDLER_ID); // In the continuous mode, the cursor speed in physical display is kept. // Thus, we don't consume any motion delta. return new float[]{dx, dy}; } } services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +94 −0 Original line number Diff line number Diff line Loading @@ -30,7 +30,10 @@ 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.isNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; Loading Loading @@ -247,6 +250,40 @@ public class FullScreenMagnificationControllerTest { verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER) public void testRegister_RegistersPointerMotionFilter() { register(DISPLAY_0); verify(mMockInputManager).registerAccessibilityPointerMotionFilter( any(InputManagerInternal.AccessibilityPointerMotionFilter.class)); // If a filter is already registered, adding a display won't invoke another filter // registration. clearInvocations(mMockInputManager); register(DISPLAY_1); register(INVALID_DISPLAY); verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter( any(InputManagerInternal.AccessibilityPointerMotionFilter.class)); } @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER) public void testUnregister_UnregistersPointerMotionFilter() { register(DISPLAY_0); register(DISPLAY_1); clearInvocations(mMockInputManager); mFullScreenMagnificationController.unregister(DISPLAY_1); // There's still an active display. Don't unregister yet. verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter( nullable(InputManagerInternal.AccessibilityPointerMotionFilter.class)); mFullScreenMagnificationController.unregister(DISPLAY_0); verify(mMockInputManager, times(1)).registerAccessibilityPointerMotionFilter(isNull()); } @Test public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() { for (int i = 0; i < DISPLAY_COUNT; i++) { Loading Loading @@ -698,6 +735,63 @@ public class FullScreenMagnificationControllerTest { verifyNoMoreInteractions(mMockWindowManager); } @Test public void testSetOffset_whileMagnifying_offsetsMove() { for (int i = 0; i < DISPLAY_COUNT; i++) { setOffset_whileMagnifying_offsetsMove(i); resetMockWindowManager(); } } private void setOffset_whileMagnifying_offsetsMove(int displayId) { register(displayId); PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; for (final float scale : new float[]{2.0f, 2.5f, 3.0f}) { assertTrue(mFullScreenMagnificationController .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); for (final PointF center : new PointF[]{ INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER}) { Mockito.clearInvocations(mMockWindowManager); PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale); mFullScreenMagnificationController.setOffset(displayId, newOffsets.x, newOffsets.y, SERVICE_ID_1); mMessageCapturingHandler.sendAllMessages(); MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets); verify(mMockWindowManager) .setMagnificationSpec(eq(displayId), argThat(closeTo(expectedSpec))); assertEquals(center.x, mFullScreenMagnificationController.getCenterX(displayId), 0.0); assertEquals(center.y, mFullScreenMagnificationController.getCenterY(displayId), 0.0); verify(mMockValueAnimator, times(0)).start(); } } } @Test public void testSetOffset_whileNotMagnifying_hasNoEffect() { for (int i = 0; i < DISPLAY_COUNT; i++) { setOffset_whileNotMagnifying_hasNoEffect(i); resetMockWindowManager(); } } private void setOffset_whileNotMagnifying_hasNoEffect(int displayId) { register(displayId); Mockito.reset(mMockWindowManager); MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); mFullScreenMagnificationController.setOffset(displayId, 100, 100, SERVICE_ID_1); assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); mFullScreenMagnificationController.setOffset(displayId, 200, 200, SERVICE_ID_1); assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) public void testStartFling_whileMagnifying_flings() throws InterruptedException { Loading Loading
services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +2 −1 Original line number Diff line number Diff line Loading @@ -287,7 +287,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public static final int INVALID_SERVICE_ID = -1; // Each service has an ID. Also provide one for magnification gesture handling // Each service has an ID. Also provide one for magnification gesture handling. // This ID is also used for mouse event handling. public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0; private static int sIdCounter = MAGNIFICATION_GESTURE_HANDLER_ID + 1; Loading
services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +53 −3 Original line number Diff line number Diff line Loading @@ -121,6 +121,8 @@ public class FullScreenMagnificationController implements @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier; @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier; private boolean mIsPointerMotionFilterInstalled = false; /** * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds * magnification information per display. Loading Loading @@ -830,9 +832,17 @@ public class FullScreenMagnificationController implements return; } final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { setOffset(mCurrentMagnificationSpec.offsetX - offsetX, mCurrentMagnificationSpec.offsetY - offsetY, id); } @GuardedBy("mLock") void setOffset(float offsetX, float offsetY, int id) { if (!mRegistered) { return; } if (updateCurrentSpecWithOffsetsLocked(offsetX, offsetY)) { onMagnificationChangedLocked(/* isScaleTransient= */ false); } if (id != INVALID_SERVICE_ID) { Loading Loading @@ -1065,6 +1075,7 @@ public class FullScreenMagnificationController implements if (display.register()) { mDisplays.put(displayId, display); mScreenStateObserver.registerIfNecessary(); configurePointerMotionFilter(true); } } } Loading Loading @@ -1612,6 +1623,28 @@ public class FullScreenMagnificationController implements } } /** * Sets the offset of the magnified region. * * @param displayId The logical display id. * @param offsetX the offset of the magnified region in the X coordinate, in current * screen pixels. * @param offsetY the offset of the magnified region in the Y coordinate, in current * screen pixels. * @param id the ID of the service requesting the change */ @SuppressWarnings("GuardedBy") // errorprone cannot recognize an inner class guarded by an outer class member. public void setOffset(int displayId, float offsetX, float offsetY, int id) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return; } display.setOffset(offsetX, offsetY, id); } } /** * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the * opposite direction as the offsets passed in here. Loading Loading @@ -1885,6 +1918,7 @@ public class FullScreenMagnificationController implements } if (!hasRegister) { mScreenStateObserver.unregister(); configurePointerMotionFilter(false); } } Loading @@ -1900,6 +1934,22 @@ public class FullScreenMagnificationController implements } } private void configurePointerMotionFilter(boolean enabled) { if (!Flags.enableMagnificationFollowsMouseWithPointerMotionFilter()) { return; } if (enabled == mIsPointerMotionFilterInstalled) { return; } if (!enabled) { mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter(null); } else { mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter( new FullScreenMagnificationPointerMotionEventFilter(this)); } mIsPointerMotionFilterInstalled = enabled; } private boolean traceEnabled() { return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( FLAGS_WINDOW_MANAGER_INTERNAL); Loading
services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +7 −2 Original line number Diff line number Diff line Loading @@ -182,6 +182,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final int mMinimumVelocity; private final int mMaximumVelocity; @Nullable private final MouseEventHandler mMouseEventHandler; public FullScreenMagnificationGestureHandler( Loading Loading @@ -313,7 +314,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize( R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop); mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); mMouseEventHandler = new MouseEventHandler(mFullScreenMagnificationController); mMouseEventHandler = Flags.enableMagnificationFollowsMouseWithPointerMotionFilter() ? null : new MouseEventHandler(mFullScreenMagnificationController); if (mDetectShortcutTrigger) { mScreenStateReceiver = new ScreenStateReceiver(context, this); Loading @@ -337,9 +340,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @Override void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (!mFullScreenMagnificationController.isActivated(mDisplayId)) { if (mMouseEventHandler == null || !mFullScreenMagnificationController.isActivated(mDisplayId)) { return; } // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are // over, rather than only interacting with the current display. Loading
services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.annotation.NonNull; import com.android.server.input.InputManagerInternal; /** * Handles pointer motion event for full screen magnification. * Responsible for controlling magnification's cursor following feature. */ public class FullScreenMagnificationPointerMotionEventFilter implements InputManagerInternal.AccessibilityPointerMotionFilter { private final FullScreenMagnificationController mController; public FullScreenMagnificationPointerMotionEventFilter( FullScreenMagnificationController controller) { mController = controller; } /** * This call happens on the input hot path and it is extremely performance sensitive. It * also must not call back into native code. */ @Override @NonNull public float[] filterPointerMotionEvent(float dx, float dy, float currentX, float currentY, int displayId) { if (!mController.isActivated(displayId)) { // unrelated display. return new float[]{dx, dy}; } // TODO(361817142): implement centered and edge following types. // Continuous cursor following. float scale = mController.getScale(displayId); final float newCursorX = currentX + dx; final float newCursorY = currentY + dy; mController.setOffset(displayId, newCursorX - newCursorX * scale, newCursorY - newCursorY * scale, MAGNIFICATION_GESTURE_HANDLER_ID); // In the continuous mode, the cursor speed in physical display is kept. // Thus, we don't consume any motion delta. return new float[]{dx, dy}; } }
services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +94 −0 Original line number Diff line number Diff line Loading @@ -30,7 +30,10 @@ 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.isNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; Loading Loading @@ -247,6 +250,40 @@ public class FullScreenMagnificationControllerTest { verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER) public void testRegister_RegistersPointerMotionFilter() { register(DISPLAY_0); verify(mMockInputManager).registerAccessibilityPointerMotionFilter( any(InputManagerInternal.AccessibilityPointerMotionFilter.class)); // If a filter is already registered, adding a display won't invoke another filter // registration. clearInvocations(mMockInputManager); register(DISPLAY_1); register(INVALID_DISPLAY); verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter( any(InputManagerInternal.AccessibilityPointerMotionFilter.class)); } @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER) public void testUnregister_UnregistersPointerMotionFilter() { register(DISPLAY_0); register(DISPLAY_1); clearInvocations(mMockInputManager); mFullScreenMagnificationController.unregister(DISPLAY_1); // There's still an active display. Don't unregister yet. verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter( nullable(InputManagerInternal.AccessibilityPointerMotionFilter.class)); mFullScreenMagnificationController.unregister(DISPLAY_0); verify(mMockInputManager, times(1)).registerAccessibilityPointerMotionFilter(isNull()); } @Test public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() { for (int i = 0; i < DISPLAY_COUNT; i++) { Loading Loading @@ -698,6 +735,63 @@ public class FullScreenMagnificationControllerTest { verifyNoMoreInteractions(mMockWindowManager); } @Test public void testSetOffset_whileMagnifying_offsetsMove() { for (int i = 0; i < DISPLAY_COUNT; i++) { setOffset_whileMagnifying_offsetsMove(i); resetMockWindowManager(); } } private void setOffset_whileMagnifying_offsetsMove(int displayId) { register(displayId); PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; for (final float scale : new float[]{2.0f, 2.5f, 3.0f}) { assertTrue(mFullScreenMagnificationController .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); for (final PointF center : new PointF[]{ INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER}) { Mockito.clearInvocations(mMockWindowManager); PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale); mFullScreenMagnificationController.setOffset(displayId, newOffsets.x, newOffsets.y, SERVICE_ID_1); mMessageCapturingHandler.sendAllMessages(); MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets); verify(mMockWindowManager) .setMagnificationSpec(eq(displayId), argThat(closeTo(expectedSpec))); assertEquals(center.x, mFullScreenMagnificationController.getCenterX(displayId), 0.0); assertEquals(center.y, mFullScreenMagnificationController.getCenterY(displayId), 0.0); verify(mMockValueAnimator, times(0)).start(); } } } @Test public void testSetOffset_whileNotMagnifying_hasNoEffect() { for (int i = 0; i < DISPLAY_COUNT; i++) { setOffset_whileNotMagnifying_hasNoEffect(i); resetMockWindowManager(); } } private void setOffset_whileNotMagnifying_hasNoEffect(int displayId) { register(displayId); Mockito.reset(mMockWindowManager); MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); mFullScreenMagnificationController.setOffset(displayId, 100, 100, SERVICE_ID_1); assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); mFullScreenMagnificationController.setOffset(displayId, 200, 200, SERVICE_ID_1); assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) public void testStartFling_whileMagnifying_flings() throws InterruptedException { Loading