Loading services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +117 −36 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.util.MathUtils.sqrt; import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; Loading @@ -38,7 +39,6 @@ import android.graphics.Region; import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.DisplayMetrics; Loading Loading @@ -116,8 +116,14 @@ public class MagnificationController implements MagnificationConnectionManager.C private final Executor mBackgroundExecutor; private final Handler mHandler; private @PanDirection int mActivePanDirection = PAN_DIRECTION_DOWN; // Prefer this to SystemClock, because it allows for tests to influence behavior. private SystemClock mSystemClock; private boolean[] mActivePanDirections = {false, false, false, false}; private int mActivePanDisplay = Display.INVALID_DISPLAY; // The time that panning by keyboard last took place. Since users can pan // in multiple directions at once (for example, up + left), tracking last // panned time ensures that panning doesn't occur too frequently. private long mLastPannedTime = 0; private boolean mRepeatKeysEnabled = true; private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN; Loading Loading @@ -186,6 +192,25 @@ public class MagnificationController implements MagnificationConnectionManager.C void onResult(int displayId, boolean success); } /** * Functional interface for providing time. Tests may extend this interface to "control time". */ @VisibleForTesting interface SystemClock { /** * Returns current time in milliseconds since boot, not counting time spent in deep sleep. */ long uptimeMillis(); } /** The real system clock for use in production. */ private static class SystemClockImpl implements SystemClock { @Override public long uptimeMillis() { return android.os.SystemClock.uptimeMillis(); } } /** * An interface to configure how much the magnification scale should be affected when moving in Loading Loading @@ -311,6 +336,7 @@ public class MagnificationController implements MagnificationConnectionManager.C mScaleProvider = scaleProvider; mBackgroundExecutor = backgroundExecutor; mHandler = new Handler(looper); mSystemClock = new SystemClockImpl(); LocalServices.getService(WindowManagerInternal.class) .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this); mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( Loading @@ -327,10 +353,12 @@ public class MagnificationController implements MagnificationConnectionManager.C public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, FullScreenMagnificationController fullScreenMagnificationController, MagnificationConnectionManager magnificationConnectionManager, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor, Looper looper) { MagnificationScaleProvider scaleProvider, Executor backgroundExecutor, Looper looper, SystemClock systemClock) { this(ams, lock, context, scaleProvider, backgroundExecutor, looper); mFullScreenMagnificationController = fullScreenMagnificationController; mMagnificationConnectionManager = magnificationConnectionManager; mSystemClock = systemClock; } @Override Loading Loading @@ -368,13 +396,13 @@ public class MagnificationController implements MagnificationConnectionManager.C @Override public void onPanMagnificationStart(int displayId, @MagnificationController.PanDirection int direction) { // TODO(b/355499907): Handle multiple pan gestures at the same time (e.g. user may try to // pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same // speed as non-diagonal movement. panMagnificationByStep(displayId, direction); mActivePanDirection = direction; // Update the current panning state for any callbacks. boolean isAlreadyPanning = mActivePanDisplay != Display.INVALID_DISPLAY; mActivePanDisplay = displayId; if (mRepeatKeysEnabled) { mActivePanDirections[direction] = true; // React immediately to any new key press by panning in the new composite direction. panMagnificationByStep(mActivePanDisplay, mActivePanDirections); if (!isAlreadyPanning && mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this), mInitialKeyboardRepeatIntervalMs); Loading @@ -382,9 +410,14 @@ public class MagnificationController implements MagnificationConnectionManager.C } @Override public void onPanMagnificationStop(int displayId, @MagnificationController.PanDirection int direction) { if (direction == mActivePanDirection) { public void onPanMagnificationStop(@MagnificationController.PanDirection int direction) { // Stop panning in this direction. mActivePanDirections[direction] = false; if (!mActivePanDirections[PAN_DIRECTION_LEFT] && !mActivePanDirections[PAN_DIRECTION_RIGHT] && !mActivePanDirections[PAN_DIRECTION_UP] && !mActivePanDirections[PAN_DIRECTION_DOWN]) { // Stop all panning if no more pan directions were in started. mActivePanDisplay = Display.INVALID_DISPLAY; } } Loading @@ -392,9 +425,14 @@ public class MagnificationController implements MagnificationConnectionManager.C @Override public void onScaleMagnificationStart(int displayId, @MagnificationController.ZoomDirection int direction) { scaleMagnificationByStep(displayId, direction); if (mActiveZoomDisplay != Display.INVALID_DISPLAY) { // Only allow one zoom direction at a time (even if the other keyboard // shortcut has been pressed). Return early if we are already zooming. return; } mActiveZoomDirection = direction; mActiveZoomDisplay = displayId; scaleMagnificationByStep(displayId, direction); if (mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), Loading @@ -403,16 +441,27 @@ public class MagnificationController implements MagnificationConnectionManager.C } @Override public void onScaleMagnificationStop(int displayId, @MagnificationController.ZoomDirection int direction) { public void onScaleMagnificationStop(@MagnificationController.ZoomDirection int direction) { if (direction == mActiveZoomDirection) { mActiveZoomDisplay = Display.INVALID_DISPLAY; } } @Override public void onKeyboardInteractionStop() { mActiveZoomDisplay = Display.INVALID_DISPLAY; mActivePanDisplay = Display.INVALID_DISPLAY; mActivePanDirections = new boolean[]{false, false, false, false}; } private void maybeContinuePan() { if (mActivePanDisplay != Display.INVALID_DISPLAY) { panMagnificationByStep(mActivePanDisplay, mActivePanDirection); if (mActivePanDisplay == Display.INVALID_DISPLAY) { return; } if (mSystemClock.uptimeMillis() - mLastPannedTime >= KEYBOARD_REPEAT_INTERVAL_MS) { panMagnificationByStep(mActivePanDisplay, mActivePanDirections); } if (mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this), KEYBOARD_REPEAT_INTERVAL_MS); Loading @@ -422,11 +471,14 @@ public class MagnificationController implements MagnificationConnectionManager.C private void maybeContinueZoom() { if (mActiveZoomDisplay != Display.INVALID_DISPLAY) { scaleMagnificationByStep(mActiveZoomDisplay, mActiveZoomDirection); if (mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), KEYBOARD_REPEAT_INTERVAL_MS); } } } public void setRepeatKeysEnabled(boolean isRepeatKeysEnabled) { mRepeatKeysEnabled = isRepeatKeysEnabled; Loading Loading @@ -719,7 +771,7 @@ public class MagnificationController implements MagnificationConnectionManager.C public void onWindowMagnificationActivationState(int displayId, boolean activated) { if (activated) { synchronized (mLock) { mWindowModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis()); mWindowModeEnabledTimeArray.put(displayId, mSystemClock.uptimeMillis()); setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mLastMagnificationActivatedModeArray.put(displayId, Loading @@ -733,7 +785,7 @@ public class MagnificationController implements MagnificationConnectionManager.C synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); duration = mSystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); scale = mMagnificationConnectionManager.getLastActivatedScale(displayId); } logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale); Loading Loading @@ -830,7 +882,7 @@ public class MagnificationController implements MagnificationConnectionManager.C if (activated) { synchronized (mLock) { mFullScreenModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis()); mFullScreenModeEnabledTimeArray.put(displayId, mSystemClock.uptimeMillis()); setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); mLastMagnificationActivatedModeArray.put(displayId, Loading @@ -844,7 +896,7 @@ public class MagnificationController implements MagnificationConnectionManager.C synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() duration = mSystemClock.uptimeMillis() - mFullScreenModeEnabledTimeArray.get(displayId); scale = mFullScreenMagnificationController.getLastActivatedScale(displayId); } Loading Loading @@ -1132,7 +1184,7 @@ public class MagnificationController implements MagnificationConnectionManager.C * @param displayId The logical display id. * @param direction Whether the scale should be zoomed in or out. */ public void scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { private void scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { if (getFullScreenMagnificationController().isActivated(displayId)) { final float magnificationScale = getFullScreenMagnificationController().getScale( displayId); Loading @@ -1157,9 +1209,14 @@ public class MagnificationController implements MagnificationConnectionManager.C * param. * * @param displayId The logical display id. * @param direction Whether the direction should be left/right/up/down. * @param directions The directions to pan, indexed by {@code PanDirection}. If two or more * are active, panning may be diagonal. */ public void panMagnificationByStep(int displayId, @PanDirection int direction) { private void panMagnificationByStep(int displayId, boolean[] directions) { if (directions.length != 4) { Slog.d(TAG, "Invalid number of panning directions"); return; } final boolean fullscreenActivated = getFullScreenMagnificationController().isActivated(displayId); final boolean windowActivated = Loading @@ -1168,21 +1225,43 @@ public class MagnificationController implements MagnificationConnectionManager.C return; } int numDirections = (directions[PAN_DIRECTION_LEFT] ? 1 : 0) + (directions[PAN_DIRECTION_RIGHT] ? 1 : 0) + (directions[PAN_DIRECTION_UP] ? 1 : 0) + (directions[PAN_DIRECTION_DOWN] ? 1 : 0); if (numDirections == 0) { return; } final float scale = fullscreenActivated ? getFullScreenMagnificationController().getScale(displayId) : getMagnificationConnectionManager().getScale(displayId); final float step = mPanStepProvider.nextPanStep(scale, displayId); float step = mPanStepProvider.nextPanStep(scale, displayId); // If the user is trying to pan diagonally (2 directions), divide by the sqrt(2) // so that the apparent step length (the radius of the step) is the same as // panning in just one direction. // Note that if numDirections is 3 or 4, opposite directions will cancel and // there's no need to rescale {@code step}. if (numDirections == 2) { step /= sqrt(2); } // If two directions cancel out, they will be added and subtracted below for net change 0. // This makes the logic simpler than removing out opposite directions manually. float offsetX = 0; float offsetY = 0; if (direction == PAN_DIRECTION_LEFT) { offsetX = -step; } else if (direction == PAN_DIRECTION_RIGHT) { offsetX = step; } else if (direction == PAN_DIRECTION_UP) { offsetY = -step; } else if (direction == PAN_DIRECTION_DOWN) { offsetY = step; if (directions[PAN_DIRECTION_LEFT]) { offsetX -= step; } if (directions[PAN_DIRECTION_RIGHT]) { offsetX += step; } if (directions[PAN_DIRECTION_UP]) { offsetY -= step; } if (directions[PAN_DIRECTION_DOWN]) { offsetY += step; } if (fullscreenActivated) { Loading @@ -1194,6 +1273,8 @@ public class MagnificationController implements MagnificationConnectionManager.C getMagnificationConnectionManager().moveWindowMagnification(displayId, offsetX, offsetY); } mLastPannedTime = mSystemClock.uptimeMillis(); } private final class DisableMagnificationCallback implements Loading services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java +18 −8 Original line number Diff line number Diff line Loading @@ -46,10 +46,8 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { * arrows had been pressed at the same time (e.g. diagonal panning). * * @param displayId The logical display ID * @param direction The direction in which panning stopped */ void onPanMagnificationStop(int displayId, @MagnificationController.PanDirection int direction); void onPanMagnificationStop(int displayId); /** * Called when a keyboard shortcut to scale magnification in direction `direction` is Loading @@ -65,14 +63,18 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { * Called when a keyboard shortcut to scale magnification in direction `direction` is * unpressed by a user. * * @param displayId The logical display ID * @param direction The direction in which scaling stopped */ void onScaleMagnificationStop(int displayId, @MagnificationController.ZoomDirection int direction); void onScaleMagnificationStop(@MagnificationController.ZoomDirection int direction); /** * Called when all keyboard interaction with magnification should be stopped. */ void onKeyboardInteractionStop(); } protected final MagnificationKeyHandler.Callback mCallback; private boolean mIsKeyboardInteracting = false; public MagnificationKeyHandler(Callback callback) { mCallback = callback; Loading @@ -88,6 +90,12 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { boolean modifiersPressed = event.isAltPressed() && event.isMetaPressed(); if (!modifiersPressed) { super.onKeyEvent(event, policyFlags); if (mIsKeyboardInteracting) { // When modifier keys are no longer pressed, ensure that scaling and // panning are fully stopped. mCallback.onKeyboardInteractionStop(); mIsKeyboardInteracting = false; } return; } boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; Loading @@ -102,8 +110,9 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { }; if (isDown) { mCallback.onPanMagnificationStart(getDisplayId(event), panDirection); mIsKeyboardInteracting = true; } else { mCallback.onPanMagnificationStop(getDisplayId(event), panDirection); mCallback.onPanMagnificationStop(panDirection); } return; } else if (keyCode == KeyEvent.KEYCODE_EQUALS || keyCode == KeyEvent.KEYCODE_MINUS) { Loading @@ -113,8 +122,9 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { } if (isDown) { mCallback.onScaleMagnificationStart(getDisplayId(event), zoomDirection); mIsKeyboardInteracting = true; } else { mCallback.onScaleMagnificationStop(getDisplayId(event), zoomDirection); mCallback.onScaleMagnificationStop(zoomDirection); } return; } Loading services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +566 −48 File changed.Preview size limit exceeded, changes collapsed. Show changes services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java +68 −31 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; /** Loading Loading @@ -78,9 +79,10 @@ public class MagnificationKeyHandlerTest { // No callbacks were called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); // The event was passed on. verify(mNextHandler, times(1)).onKeyEvent(event, 0); Loading @@ -95,9 +97,10 @@ public class MagnificationKeyHandlerTest { // No callbacks were called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); // The event was passed on. verify(mNextHandler, times(1)).onKeyEvent(event, 0); Loading @@ -112,9 +115,10 @@ public class MagnificationKeyHandlerTest { // No callbacks were called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); // The event was passed on. verify(mNextHandler, times(1)).onKeyEvent(event, 0); Loading Loading @@ -157,48 +161,79 @@ public class MagnificationKeyHandlerTest { mMkh.onKeyEvent(downLeftEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); // Also press the down arrow key. final KeyEvent downDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(downDownEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); // Lift the left arrow key. final KeyEvent upLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upLeftEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(0)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(1)).onPanMagnificationStop(PAN_DIRECTION_LEFT); verify(mCallback, times(0)).onPanMagnificationStop(PAN_DIRECTION_DOWN); Mockito.clearInvocations(mCallback); // Lift the down arrow key. final KeyEvent upDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upDownEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStop(PAN_DIRECTION_DOWN); // The event was not passed on. verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); } @Test public void testPanMagnification_modifiersReleasedBeforeArrows() { final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(downEvent, 0); // Pan started. verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); Mockito.clearInvocations(mCallback); // Lift the "meta" key. final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_META_LEFT, 0, KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upEvent, 0); // Pan ended. verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(1)).onKeyboardInteractionStop(); } private void testPanMagnification(int keyCode, int panDirection) { final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); Loading @@ -206,19 +241,21 @@ public class MagnificationKeyHandlerTest { // Pan started. verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upEvent, 0); // Pan ended. verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(1)).onPanMagnificationStop(panDirection); // Scale callbacks were not called. verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); // The events were not passed on. verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); Loading @@ -232,25 +269,25 @@ public class MagnificationKeyHandlerTest { // Scale started. verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, zoomDirection); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upEvent, 0); // Scale ended. verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, zoomDirection); verify(mCallback, times(1)).onScaleMagnificationStop(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, zoomDirection); verify(mCallback, times(1)).onScaleMagnificationStop(zoomDirection); // Pan callbacks were not called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); // The events were not passed on. verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); } } Loading
services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +117 −36 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.util.MathUtils.sqrt; import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; Loading @@ -38,7 +39,6 @@ import android.graphics.Region; import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.DisplayMetrics; Loading Loading @@ -116,8 +116,14 @@ public class MagnificationController implements MagnificationConnectionManager.C private final Executor mBackgroundExecutor; private final Handler mHandler; private @PanDirection int mActivePanDirection = PAN_DIRECTION_DOWN; // Prefer this to SystemClock, because it allows for tests to influence behavior. private SystemClock mSystemClock; private boolean[] mActivePanDirections = {false, false, false, false}; private int mActivePanDisplay = Display.INVALID_DISPLAY; // The time that panning by keyboard last took place. Since users can pan // in multiple directions at once (for example, up + left), tracking last // panned time ensures that panning doesn't occur too frequently. private long mLastPannedTime = 0; private boolean mRepeatKeysEnabled = true; private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN; Loading Loading @@ -186,6 +192,25 @@ public class MagnificationController implements MagnificationConnectionManager.C void onResult(int displayId, boolean success); } /** * Functional interface for providing time. Tests may extend this interface to "control time". */ @VisibleForTesting interface SystemClock { /** * Returns current time in milliseconds since boot, not counting time spent in deep sleep. */ long uptimeMillis(); } /** The real system clock for use in production. */ private static class SystemClockImpl implements SystemClock { @Override public long uptimeMillis() { return android.os.SystemClock.uptimeMillis(); } } /** * An interface to configure how much the magnification scale should be affected when moving in Loading Loading @@ -311,6 +336,7 @@ public class MagnificationController implements MagnificationConnectionManager.C mScaleProvider = scaleProvider; mBackgroundExecutor = backgroundExecutor; mHandler = new Handler(looper); mSystemClock = new SystemClockImpl(); LocalServices.getService(WindowManagerInternal.class) .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this); mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( Loading @@ -327,10 +353,12 @@ public class MagnificationController implements MagnificationConnectionManager.C public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, FullScreenMagnificationController fullScreenMagnificationController, MagnificationConnectionManager magnificationConnectionManager, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor, Looper looper) { MagnificationScaleProvider scaleProvider, Executor backgroundExecutor, Looper looper, SystemClock systemClock) { this(ams, lock, context, scaleProvider, backgroundExecutor, looper); mFullScreenMagnificationController = fullScreenMagnificationController; mMagnificationConnectionManager = magnificationConnectionManager; mSystemClock = systemClock; } @Override Loading Loading @@ -368,13 +396,13 @@ public class MagnificationController implements MagnificationConnectionManager.C @Override public void onPanMagnificationStart(int displayId, @MagnificationController.PanDirection int direction) { // TODO(b/355499907): Handle multiple pan gestures at the same time (e.g. user may try to // pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same // speed as non-diagonal movement. panMagnificationByStep(displayId, direction); mActivePanDirection = direction; // Update the current panning state for any callbacks. boolean isAlreadyPanning = mActivePanDisplay != Display.INVALID_DISPLAY; mActivePanDisplay = displayId; if (mRepeatKeysEnabled) { mActivePanDirections[direction] = true; // React immediately to any new key press by panning in the new composite direction. panMagnificationByStep(mActivePanDisplay, mActivePanDirections); if (!isAlreadyPanning && mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this), mInitialKeyboardRepeatIntervalMs); Loading @@ -382,9 +410,14 @@ public class MagnificationController implements MagnificationConnectionManager.C } @Override public void onPanMagnificationStop(int displayId, @MagnificationController.PanDirection int direction) { if (direction == mActivePanDirection) { public void onPanMagnificationStop(@MagnificationController.PanDirection int direction) { // Stop panning in this direction. mActivePanDirections[direction] = false; if (!mActivePanDirections[PAN_DIRECTION_LEFT] && !mActivePanDirections[PAN_DIRECTION_RIGHT] && !mActivePanDirections[PAN_DIRECTION_UP] && !mActivePanDirections[PAN_DIRECTION_DOWN]) { // Stop all panning if no more pan directions were in started. mActivePanDisplay = Display.INVALID_DISPLAY; } } Loading @@ -392,9 +425,14 @@ public class MagnificationController implements MagnificationConnectionManager.C @Override public void onScaleMagnificationStart(int displayId, @MagnificationController.ZoomDirection int direction) { scaleMagnificationByStep(displayId, direction); if (mActiveZoomDisplay != Display.INVALID_DISPLAY) { // Only allow one zoom direction at a time (even if the other keyboard // shortcut has been pressed). Return early if we are already zooming. return; } mActiveZoomDirection = direction; mActiveZoomDisplay = displayId; scaleMagnificationByStep(displayId, direction); if (mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), Loading @@ -403,16 +441,27 @@ public class MagnificationController implements MagnificationConnectionManager.C } @Override public void onScaleMagnificationStop(int displayId, @MagnificationController.ZoomDirection int direction) { public void onScaleMagnificationStop(@MagnificationController.ZoomDirection int direction) { if (direction == mActiveZoomDirection) { mActiveZoomDisplay = Display.INVALID_DISPLAY; } } @Override public void onKeyboardInteractionStop() { mActiveZoomDisplay = Display.INVALID_DISPLAY; mActivePanDisplay = Display.INVALID_DISPLAY; mActivePanDirections = new boolean[]{false, false, false, false}; } private void maybeContinuePan() { if (mActivePanDisplay != Display.INVALID_DISPLAY) { panMagnificationByStep(mActivePanDisplay, mActivePanDirection); if (mActivePanDisplay == Display.INVALID_DISPLAY) { return; } if (mSystemClock.uptimeMillis() - mLastPannedTime >= KEYBOARD_REPEAT_INTERVAL_MS) { panMagnificationByStep(mActivePanDisplay, mActivePanDirections); } if (mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this), KEYBOARD_REPEAT_INTERVAL_MS); Loading @@ -422,11 +471,14 @@ public class MagnificationController implements MagnificationConnectionManager.C private void maybeContinueZoom() { if (mActiveZoomDisplay != Display.INVALID_DISPLAY) { scaleMagnificationByStep(mActiveZoomDisplay, mActiveZoomDirection); if (mRepeatKeysEnabled) { mHandler.sendMessageDelayed( PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this), KEYBOARD_REPEAT_INTERVAL_MS); } } } public void setRepeatKeysEnabled(boolean isRepeatKeysEnabled) { mRepeatKeysEnabled = isRepeatKeysEnabled; Loading Loading @@ -719,7 +771,7 @@ public class MagnificationController implements MagnificationConnectionManager.C public void onWindowMagnificationActivationState(int displayId, boolean activated) { if (activated) { synchronized (mLock) { mWindowModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis()); mWindowModeEnabledTimeArray.put(displayId, mSystemClock.uptimeMillis()); setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mLastMagnificationActivatedModeArray.put(displayId, Loading @@ -733,7 +785,7 @@ public class MagnificationController implements MagnificationConnectionManager.C synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); duration = mSystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); scale = mMagnificationConnectionManager.getLastActivatedScale(displayId); } logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale); Loading Loading @@ -830,7 +882,7 @@ public class MagnificationController implements MagnificationConnectionManager.C if (activated) { synchronized (mLock) { mFullScreenModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis()); mFullScreenModeEnabledTimeArray.put(displayId, mSystemClock.uptimeMillis()); setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); mLastMagnificationActivatedModeArray.put(displayId, Loading @@ -844,7 +896,7 @@ public class MagnificationController implements MagnificationConnectionManager.C synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() duration = mSystemClock.uptimeMillis() - mFullScreenModeEnabledTimeArray.get(displayId); scale = mFullScreenMagnificationController.getLastActivatedScale(displayId); } Loading Loading @@ -1132,7 +1184,7 @@ public class MagnificationController implements MagnificationConnectionManager.C * @param displayId The logical display id. * @param direction Whether the scale should be zoomed in or out. */ public void scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { private void scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { if (getFullScreenMagnificationController().isActivated(displayId)) { final float magnificationScale = getFullScreenMagnificationController().getScale( displayId); Loading @@ -1157,9 +1209,14 @@ public class MagnificationController implements MagnificationConnectionManager.C * param. * * @param displayId The logical display id. * @param direction Whether the direction should be left/right/up/down. * @param directions The directions to pan, indexed by {@code PanDirection}. If two or more * are active, panning may be diagonal. */ public void panMagnificationByStep(int displayId, @PanDirection int direction) { private void panMagnificationByStep(int displayId, boolean[] directions) { if (directions.length != 4) { Slog.d(TAG, "Invalid number of panning directions"); return; } final boolean fullscreenActivated = getFullScreenMagnificationController().isActivated(displayId); final boolean windowActivated = Loading @@ -1168,21 +1225,43 @@ public class MagnificationController implements MagnificationConnectionManager.C return; } int numDirections = (directions[PAN_DIRECTION_LEFT] ? 1 : 0) + (directions[PAN_DIRECTION_RIGHT] ? 1 : 0) + (directions[PAN_DIRECTION_UP] ? 1 : 0) + (directions[PAN_DIRECTION_DOWN] ? 1 : 0); if (numDirections == 0) { return; } final float scale = fullscreenActivated ? getFullScreenMagnificationController().getScale(displayId) : getMagnificationConnectionManager().getScale(displayId); final float step = mPanStepProvider.nextPanStep(scale, displayId); float step = mPanStepProvider.nextPanStep(scale, displayId); // If the user is trying to pan diagonally (2 directions), divide by the sqrt(2) // so that the apparent step length (the radius of the step) is the same as // panning in just one direction. // Note that if numDirections is 3 or 4, opposite directions will cancel and // there's no need to rescale {@code step}. if (numDirections == 2) { step /= sqrt(2); } // If two directions cancel out, they will be added and subtracted below for net change 0. // This makes the logic simpler than removing out opposite directions manually. float offsetX = 0; float offsetY = 0; if (direction == PAN_DIRECTION_LEFT) { offsetX = -step; } else if (direction == PAN_DIRECTION_RIGHT) { offsetX = step; } else if (direction == PAN_DIRECTION_UP) { offsetY = -step; } else if (direction == PAN_DIRECTION_DOWN) { offsetY = step; if (directions[PAN_DIRECTION_LEFT]) { offsetX -= step; } if (directions[PAN_DIRECTION_RIGHT]) { offsetX += step; } if (directions[PAN_DIRECTION_UP]) { offsetY -= step; } if (directions[PAN_DIRECTION_DOWN]) { offsetY += step; } if (fullscreenActivated) { Loading @@ -1194,6 +1273,8 @@ public class MagnificationController implements MagnificationConnectionManager.C getMagnificationConnectionManager().moveWindowMagnification(displayId, offsetX, offsetY); } mLastPannedTime = mSystemClock.uptimeMillis(); } private final class DisableMagnificationCallback implements Loading
services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java +18 −8 Original line number Diff line number Diff line Loading @@ -46,10 +46,8 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { * arrows had been pressed at the same time (e.g. diagonal panning). * * @param displayId The logical display ID * @param direction The direction in which panning stopped */ void onPanMagnificationStop(int displayId, @MagnificationController.PanDirection int direction); void onPanMagnificationStop(int displayId); /** * Called when a keyboard shortcut to scale magnification in direction `direction` is Loading @@ -65,14 +63,18 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { * Called when a keyboard shortcut to scale magnification in direction `direction` is * unpressed by a user. * * @param displayId The logical display ID * @param direction The direction in which scaling stopped */ void onScaleMagnificationStop(int displayId, @MagnificationController.ZoomDirection int direction); void onScaleMagnificationStop(@MagnificationController.ZoomDirection int direction); /** * Called when all keyboard interaction with magnification should be stopped. */ void onKeyboardInteractionStop(); } protected final MagnificationKeyHandler.Callback mCallback; private boolean mIsKeyboardInteracting = false; public MagnificationKeyHandler(Callback callback) { mCallback = callback; Loading @@ -88,6 +90,12 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { boolean modifiersPressed = event.isAltPressed() && event.isMetaPressed(); if (!modifiersPressed) { super.onKeyEvent(event, policyFlags); if (mIsKeyboardInteracting) { // When modifier keys are no longer pressed, ensure that scaling and // panning are fully stopped. mCallback.onKeyboardInteractionStop(); mIsKeyboardInteracting = false; } return; } boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; Loading @@ -102,8 +110,9 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { }; if (isDown) { mCallback.onPanMagnificationStart(getDisplayId(event), panDirection); mIsKeyboardInteracting = true; } else { mCallback.onPanMagnificationStop(getDisplayId(event), panDirection); mCallback.onPanMagnificationStop(panDirection); } return; } else if (keyCode == KeyEvent.KEYCODE_EQUALS || keyCode == KeyEvent.KEYCODE_MINUS) { Loading @@ -113,8 +122,9 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { } if (isDown) { mCallback.onScaleMagnificationStart(getDisplayId(event), zoomDirection); mIsKeyboardInteracting = true; } else { mCallback.onScaleMagnificationStop(getDisplayId(event), zoomDirection); mCallback.onScaleMagnificationStop(zoomDirection); } return; } Loading
services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +566 −48 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java +68 −31 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; /** Loading Loading @@ -78,9 +79,10 @@ public class MagnificationKeyHandlerTest { // No callbacks were called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); // The event was passed on. verify(mNextHandler, times(1)).onKeyEvent(event, 0); Loading @@ -95,9 +97,10 @@ public class MagnificationKeyHandlerTest { // No callbacks were called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); // The event was passed on. verify(mNextHandler, times(1)).onKeyEvent(event, 0); Loading @@ -112,9 +115,10 @@ public class MagnificationKeyHandlerTest { // No callbacks were called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); // The event was passed on. verify(mNextHandler, times(1)).onKeyEvent(event, 0); Loading Loading @@ -157,48 +161,79 @@ public class MagnificationKeyHandlerTest { mMkh.onKeyEvent(downLeftEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); // Also press the down arrow key. final KeyEvent downDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(downDownEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); // Lift the left arrow key. final KeyEvent upLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upLeftEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(0)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(1)).onPanMagnificationStop(PAN_DIRECTION_LEFT); verify(mCallback, times(0)).onPanMagnificationStop(PAN_DIRECTION_DOWN); Mockito.clearInvocations(mCallback); // Lift the down arrow key. final KeyEvent upDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upDownEvent, 0); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(PAN_DIRECTION_LEFT); verify(mCallback, times(1)).onPanMagnificationStop(PAN_DIRECTION_DOWN); // The event was not passed on. verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); } @Test public void testPanMagnification_modifiersReleasedBeforeArrows() { final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(downEvent, 0); // Pan started. verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(0)).onKeyboardInteractionStop(); Mockito.clearInvocations(mCallback); // Lift the "meta" key. final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_META_LEFT, 0, KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upEvent, 0); // Pan ended. verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, PAN_DIRECTION_DOWN); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); verify(mCallback, times(1)).onKeyboardInteractionStop(); } private void testPanMagnification(int keyCode, int panDirection) { final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); Loading @@ -206,19 +241,21 @@ public class MagnificationKeyHandlerTest { // Pan started. verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upEvent, 0); // Pan ended. verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(0)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); verify(mCallback, times(1)).onPanMagnificationStop(panDirection); // Scale callbacks were not called. verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); // The events were not passed on. verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); Loading @@ -232,25 +269,25 @@ public class MagnificationKeyHandlerTest { // Scale started. verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, zoomDirection); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onScaleMagnificationStop(anyInt()); Mockito.clearInvocations(mCallback); final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); mMkh.onKeyEvent(upEvent, 0); // Scale ended. verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, zoomDirection); verify(mCallback, times(1)).onScaleMagnificationStop(Display.DEFAULT_DISPLAY, verify(mCallback, times(0)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, zoomDirection); verify(mCallback, times(1)).onScaleMagnificationStop(zoomDirection); // Pan callbacks were not called. verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); verify(mCallback, times(0)).onPanMagnificationStop(anyInt()); // The events were not passed on. verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); } }