Loading services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +202 −22 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import static java.util.Arrays.asList; import static java.util.Arrays.copyOfRange; import android.accessibilityservice.MagnificationConfig; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; Loading @@ -41,6 +42,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; import android.os.Looper; Loading Loading @@ -147,6 +149,20 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; @VisibleForTesting static final int OVERSCROLL_NONE = 0; @VisibleForTesting static final int OVERSCROLL_LEFT_EDGE = 1; @VisibleForTesting static final int OVERSCROLL_RIGHT_EDGE = 2; @VisibleForTesting static final int OVERSCROLL_VERTICAL_EDGE = 3; @IntDef({ OVERSCROLL_NONE, OVERSCROLL_LEFT_EDGE, OVERSCROLL_RIGHT_EDGE, OVERSCROLL_VERTICAL_EDGE }) public @interface OverscrollState {} @VisibleForTesting boolean mIsSinglePanningEnabled; public FullScreenMagnificationGestureHandler(@UiContext Context context, FullScreenMagnificationController fullScreenMagnificationController, Loading Loading @@ -842,8 +858,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH transitToPanningScalingStateAndClear(); } else if (mIsSinglePanningEnabled && isActivated() && event.getPointerCount() == 1 && !isOverscroll(event)) { && event.getPointerCount() == 1) { if (overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); } transitToSinglePanningStateAndClear(); } else { transitionToDelegatingStateAndClear(); Loading Loading @@ -881,20 +900,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } private boolean isOverscroll(MotionEvent event) { if (!pointerDownValid(mFirstPointerDownLocation)) { return false; } float dX = event.getX() - mFirstPointerDownLocation.x; float dY = event.getY() - mFirstPointerDownLocation.y; boolean didOverscroll = mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) && dX > 0 || mFullScreenMagnificationController.isAtRightEdge(mDisplayId) && dX < 0 || mFullScreenMagnificationController.isAtTopEdge(mDisplayId) && dY > 0 || mFullScreenMagnificationController.isAtBottomEdge(mDisplayId) && dY < 0; return didOverscroll; } private void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) { final int index = event.getActionIndex(); pointerDownLocation.set(event.getX(index), event.getY(index)); Loading Loading @@ -1172,7 +1177,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } private void zoomOn(float centerX, float centerY) { if (DEBUG_DETECTING) Slog.i(mLogTag, "zoomOn(" + centerX + ", " + centerY + ")"); if (DEBUG_DETECTING) { Slog.i(mLogTag, "zoomOn(" + centerX + ", " + centerY + ")"); } final float scale = MathUtils.constrain( mFullScreenMagnificationController.getPersistedScale(mDisplayId), Loading @@ -1189,7 +1196,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } private void zoomOff() { if (DEBUG_DETECTING) Slog.i(mLogTag, "zoomOff()"); if (DEBUG_DETECTING) { Slog.i(mLogTag, "zoomOff()"); } mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true); } Loading Loading @@ -1218,6 +1227,28 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + '}'; } private int overscrollState(MotionEvent event, PointF firstPointerDownLocation) { if (!pointerValid(firstPointerDownLocation)) { return OVERSCROLL_NONE; } float dX = event.getX() - firstPointerDownLocation.x; float dY = event.getY() - firstPointerDownLocation.y; if (mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) && dX > 0) { return OVERSCROLL_LEFT_EDGE; } else if (mFullScreenMagnificationController.isAtRightEdge(mDisplayId) && dX < 0) { return OVERSCROLL_RIGHT_EDGE; } else if (mFullScreenMagnificationController.isAtTopEdge(mDisplayId) && dY > 0 || mFullScreenMagnificationController.isAtBottomEdge(mDisplayId) && dY < 0) { return OVERSCROLL_VERTICAL_EDGE; } return OVERSCROLL_NONE; } private boolean pointerValid(PointF pointerDownLocation) { return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN(pointerDownLocation.y)); } private static final class MotionEventInfo { private static final int MAX_POOL_SIZE = 10; Loading Loading @@ -1343,10 +1374,29 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper; SinglePanningState(Context context, FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { @VisibleForTesting int mOverscrollState; // mPivotEdge is the point on the edge of the screen when the magnified view hits the edge // This point sets the center of magnified view when warp/scale effect is triggered private final PointF mPivotEdge; // mReachedEdgeCoord is the user's pointer location on the screen when the magnified view // has hit the edge private final PointF mReachedEdgeCoord; // mEdgeCooldown value will be set to true when user hits the edge and will be set to false // once the user moves x distance away from the edge. This is so that vibrating haptic // doesn't get triggered by slight movements private boolean mEdgeCooldown; SinglePanningState( Context context, FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain()); mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper; mOverscrollState = OVERSCROLL_NONE; mPivotEdge = new PointF(Float.NaN, Float.NaN); mReachedEdgeCoord = new PointF(Float.NaN, Float.NaN); mEdgeCooldown = false; } @Override Loading @@ -1355,6 +1405,16 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH switch (action) { case ACTION_UP: case ACTION_CANCEL: if (mOverscrollState == OVERSCROLL_LEFT_EDGE || mOverscrollState == OVERSCROLL_RIGHT_EDGE) { mFullScreenMagnificationController.setScaleAndCenter( mDisplayId, mFullScreenMagnificationController.getPersistedScale(mDisplayId), mPivotEdge.x, mPivotEdge.y, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); } clear(); transitionTo(mDetectingState); break; Loading Loading @@ -1382,11 +1442,22 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + " isAtEdge: " + mFullScreenMagnificationController.isAtEdge(mDisplayId)); } // TODO: b/280812104 Dispatch events before Delegation if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) { playEdgeVibration(second); setPivotEdge(); } if (mOverscrollState == OVERSCROLL_NONE) { mOverscrollState = overscrollState(second, new PointF(first.getX(), first.getY())); } else if (mOverscrollState == OVERSCROLL_VERTICAL_EDGE) { clear(); transitionTo(mDelegatingState); vibrateIfNeeded(); } else { boolean reset = warpEffectReset(second); if (reset) { mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true); clear(); transitionTo(mDetectingState); } } return /* event consumed: */ true; } Loading @@ -1405,7 +1476,116 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH return "SinglePanningState{" + "isEdgeOfView=" + mFullScreenMagnificationController.isAtEdge(mDisplayId) + "overscrollStatus=" + mOverscrollState + "}"; } private void playEdgeVibration(MotionEvent event) { if (mOverscrollState == OVERSCROLL_NONE) { vibrateIfNeeded(event); } } private void setPivotEdge() { if (!pointerValid(mPivotEdge)) { Rect bounds = new Rect(); mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds); if (mOverscrollState == OVERSCROLL_LEFT_EDGE) { mPivotEdge.set( bounds.left, mFullScreenMagnificationController.getCenterY(mDisplayId)); } else if (mOverscrollState == OVERSCROLL_RIGHT_EDGE) { mPivotEdge.set( bounds.right, mFullScreenMagnificationController.getCenterY(mDisplayId)); } } } private boolean warpEffectReset(MotionEvent second) { float scale = calculateOverscrollScale(second); if (scale < 0) return false; mFullScreenMagnificationController.setScaleAndCenter( /* displayId= */ mDisplayId, /* scale= */ scale, /* centerX= */ mPivotEdge.x, /* centerY= */ mPivotEdge.y, /* animate= */ true, /* id= */ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); if (scale == 1.0f) { return true; } return false; } private float calculateOverscrollScale(MotionEvent second) { // if at left and overshootDistX is negative or if at right and overshootDistX is // positive then user is not in overscroll state anymore overscroll state. Reset // overscroll values by clearing float overshootDistX = second.getX() - mReachedEdgeCoord.x; if ((mOverscrollState == OVERSCROLL_LEFT_EDGE && overshootDistX < 0) || (mOverscrollState == OVERSCROLL_RIGHT_EDGE && overshootDistX > 0)) { clear(); return -1.0f; } float overshootDistY = second.getY() - mReachedEdgeCoord.y; float overshootDist = (float) (Math.hypot(abs(overshootDistX), abs(overshootDistY))); Rect bounds = new Rect(); mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds); float overShootFraction = overshootDist / (float) bounds.width(); float minDist = 0.05f * bounds.width(); if (mEdgeCooldown && (overshootDist > minDist)) { mEdgeCooldown = false; } float scale = (1 - overShootFraction) * getSensitivityScale(); scale = MathUtils.constrain( /* amount= */ scale, /* low= */ 1.0f, /* high= */ mFullScreenMagnificationController.getPersistedScale( mDisplayId)); return scale; } private float getSensitivityScale() { float magnificationScale = mFullScreenMagnificationController.getPersistedScale(mDisplayId); float sensitivityFactor = 0.0f; if (magnificationScale < 1.7f) { sensitivityFactor = 1.0f; } else if (magnificationScale < 2.0f) { sensitivityFactor = 1.0f; } else if (magnificationScale < 2.2f) { sensitivityFactor = 0.95f; } else if (magnificationScale < 2.5f) { sensitivityFactor = 1.1f; } else if (magnificationScale < 2.7f) { sensitivityFactor = 1.3f; } else if (magnificationScale < 3.0f) { sensitivityFactor = 1.0f; } else { sensitivityFactor = 1.0f; } return magnificationScale * sensitivityFactor; } private void vibrateIfNeeded(MotionEvent event) { if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) || mFullScreenMagnificationController.isAtRightEdge(mDisplayId)) && !mEdgeCooldown) { mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); mReachedEdgeCoord.set(event.getX(), event.getY()); mEdgeCooldown = true; } } @Override public void clear() { mOverscrollState = OVERSCROLL_NONE; mPivotEdge.set(Float.NaN, Float.NaN); mReachedEdgeCoord.set(Float.NaN, Float.NaN); mEdgeCooldown = false; } } } services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +68 −23 Original line number Diff line number Diff line Loading @@ -589,51 +589,91 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test public void testScroll_zoomedStateAndAtEdge_delegatingState() { goFromStateIdleTo(STATE_ACTIVATED); @FlakyTest public void testScroll_singleHorizontalPanningAndAtEdge_leftEdgeOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, INITIAL_MAGNIFICATION_BOUNDS.top / 2, false, 1); DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, centerY, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF endCoords = new PointF(initCoords.x, initCoords.y); endCoords.offset(swipeMinDistance, 0); allowEventDelegation(); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(swipeMinDistance, 0); swipeAndHold(initCoords, endCoords); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_LEFT_EDGE); assertTrue(isZoomed()); } @Test @FlakyTest public void testScroll_singlePanningAndAtEdge_delegatingState() { public void testScroll_singleHorizontalPanningAndAtEdge_rightEdgeOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, INITIAL_MAGNIFICATION_BOUNDS.top / 2, false, 1); DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.right, centerY, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF endCoords = new PointF(initCoords.x, initCoords.y); endCoords.offset(swipeMinDistance, 0); allowEventDelegation(); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(-swipeMinDistance, 0); swipeAndHold(initCoords, endCoords); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_RIGHT_EDGE); assertTrue(isZoomed()); } @Test @FlakyTest public void testScroll_singleVerticalPanningAndAtEdge_verticalOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerX = (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, centerX, INITIAL_MAGNIFICATION_BOUNDS.top, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(0, swipeMinDistance); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE); assertTrue(isZoomed()); } @Test public void testScroll_singlePanningAndAtEdge_noOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, centerY, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(-swipeMinDistance, 0); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_NONE); assertTrue(isZoomed()); } Loading Loading @@ -1105,6 +1145,11 @@ public class FullScreenMagnificationGestureHandlerTest { send(upEvent()); } private void swipe(PointF start, PointF end) { swipeAndHold(start, end); send(upEvent(end.x, end.y)); } private void swipeAndHold() { send(downEvent()); send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2)); Loading Loading
services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +202 −22 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import static java.util.Arrays.asList; import static java.util.Arrays.copyOfRange; import android.accessibilityservice.MagnificationConfig; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; Loading @@ -41,6 +42,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; import android.os.Looper; Loading Loading @@ -147,6 +149,20 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; @VisibleForTesting static final int OVERSCROLL_NONE = 0; @VisibleForTesting static final int OVERSCROLL_LEFT_EDGE = 1; @VisibleForTesting static final int OVERSCROLL_RIGHT_EDGE = 2; @VisibleForTesting static final int OVERSCROLL_VERTICAL_EDGE = 3; @IntDef({ OVERSCROLL_NONE, OVERSCROLL_LEFT_EDGE, OVERSCROLL_RIGHT_EDGE, OVERSCROLL_VERTICAL_EDGE }) public @interface OverscrollState {} @VisibleForTesting boolean mIsSinglePanningEnabled; public FullScreenMagnificationGestureHandler(@UiContext Context context, FullScreenMagnificationController fullScreenMagnificationController, Loading Loading @@ -842,8 +858,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH transitToPanningScalingStateAndClear(); } else if (mIsSinglePanningEnabled && isActivated() && event.getPointerCount() == 1 && !isOverscroll(event)) { && event.getPointerCount() == 1) { if (overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); } transitToSinglePanningStateAndClear(); } else { transitionToDelegatingStateAndClear(); Loading Loading @@ -881,20 +900,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } private boolean isOverscroll(MotionEvent event) { if (!pointerDownValid(mFirstPointerDownLocation)) { return false; } float dX = event.getX() - mFirstPointerDownLocation.x; float dY = event.getY() - mFirstPointerDownLocation.y; boolean didOverscroll = mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) && dX > 0 || mFullScreenMagnificationController.isAtRightEdge(mDisplayId) && dX < 0 || mFullScreenMagnificationController.isAtTopEdge(mDisplayId) && dY > 0 || mFullScreenMagnificationController.isAtBottomEdge(mDisplayId) && dY < 0; return didOverscroll; } private void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) { final int index = event.getActionIndex(); pointerDownLocation.set(event.getX(index), event.getY(index)); Loading Loading @@ -1172,7 +1177,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } private void zoomOn(float centerX, float centerY) { if (DEBUG_DETECTING) Slog.i(mLogTag, "zoomOn(" + centerX + ", " + centerY + ")"); if (DEBUG_DETECTING) { Slog.i(mLogTag, "zoomOn(" + centerX + ", " + centerY + ")"); } final float scale = MathUtils.constrain( mFullScreenMagnificationController.getPersistedScale(mDisplayId), Loading @@ -1189,7 +1196,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } private void zoomOff() { if (DEBUG_DETECTING) Slog.i(mLogTag, "zoomOff()"); if (DEBUG_DETECTING) { Slog.i(mLogTag, "zoomOff()"); } mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true); } Loading Loading @@ -1218,6 +1227,28 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + '}'; } private int overscrollState(MotionEvent event, PointF firstPointerDownLocation) { if (!pointerValid(firstPointerDownLocation)) { return OVERSCROLL_NONE; } float dX = event.getX() - firstPointerDownLocation.x; float dY = event.getY() - firstPointerDownLocation.y; if (mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) && dX > 0) { return OVERSCROLL_LEFT_EDGE; } else if (mFullScreenMagnificationController.isAtRightEdge(mDisplayId) && dX < 0) { return OVERSCROLL_RIGHT_EDGE; } else if (mFullScreenMagnificationController.isAtTopEdge(mDisplayId) && dY > 0 || mFullScreenMagnificationController.isAtBottomEdge(mDisplayId) && dY < 0) { return OVERSCROLL_VERTICAL_EDGE; } return OVERSCROLL_NONE; } private boolean pointerValid(PointF pointerDownLocation) { return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN(pointerDownLocation.y)); } private static final class MotionEventInfo { private static final int MAX_POOL_SIZE = 10; Loading Loading @@ -1343,10 +1374,29 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper; SinglePanningState(Context context, FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { @VisibleForTesting int mOverscrollState; // mPivotEdge is the point on the edge of the screen when the magnified view hits the edge // This point sets the center of magnified view when warp/scale effect is triggered private final PointF mPivotEdge; // mReachedEdgeCoord is the user's pointer location on the screen when the magnified view // has hit the edge private final PointF mReachedEdgeCoord; // mEdgeCooldown value will be set to true when user hits the edge and will be set to false // once the user moves x distance away from the edge. This is so that vibrating haptic // doesn't get triggered by slight movements private boolean mEdgeCooldown; SinglePanningState( Context context, FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain()); mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper; mOverscrollState = OVERSCROLL_NONE; mPivotEdge = new PointF(Float.NaN, Float.NaN); mReachedEdgeCoord = new PointF(Float.NaN, Float.NaN); mEdgeCooldown = false; } @Override Loading @@ -1355,6 +1405,16 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH switch (action) { case ACTION_UP: case ACTION_CANCEL: if (mOverscrollState == OVERSCROLL_LEFT_EDGE || mOverscrollState == OVERSCROLL_RIGHT_EDGE) { mFullScreenMagnificationController.setScaleAndCenter( mDisplayId, mFullScreenMagnificationController.getPersistedScale(mDisplayId), mPivotEdge.x, mPivotEdge.y, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); } clear(); transitionTo(mDetectingState); break; Loading Loading @@ -1382,11 +1442,22 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + " isAtEdge: " + mFullScreenMagnificationController.isAtEdge(mDisplayId)); } // TODO: b/280812104 Dispatch events before Delegation if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) { playEdgeVibration(second); setPivotEdge(); } if (mOverscrollState == OVERSCROLL_NONE) { mOverscrollState = overscrollState(second, new PointF(first.getX(), first.getY())); } else if (mOverscrollState == OVERSCROLL_VERTICAL_EDGE) { clear(); transitionTo(mDelegatingState); vibrateIfNeeded(); } else { boolean reset = warpEffectReset(second); if (reset) { mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true); clear(); transitionTo(mDetectingState); } } return /* event consumed: */ true; } Loading @@ -1405,7 +1476,116 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH return "SinglePanningState{" + "isEdgeOfView=" + mFullScreenMagnificationController.isAtEdge(mDisplayId) + "overscrollStatus=" + mOverscrollState + "}"; } private void playEdgeVibration(MotionEvent event) { if (mOverscrollState == OVERSCROLL_NONE) { vibrateIfNeeded(event); } } private void setPivotEdge() { if (!pointerValid(mPivotEdge)) { Rect bounds = new Rect(); mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds); if (mOverscrollState == OVERSCROLL_LEFT_EDGE) { mPivotEdge.set( bounds.left, mFullScreenMagnificationController.getCenterY(mDisplayId)); } else if (mOverscrollState == OVERSCROLL_RIGHT_EDGE) { mPivotEdge.set( bounds.right, mFullScreenMagnificationController.getCenterY(mDisplayId)); } } } private boolean warpEffectReset(MotionEvent second) { float scale = calculateOverscrollScale(second); if (scale < 0) return false; mFullScreenMagnificationController.setScaleAndCenter( /* displayId= */ mDisplayId, /* scale= */ scale, /* centerX= */ mPivotEdge.x, /* centerY= */ mPivotEdge.y, /* animate= */ true, /* id= */ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); if (scale == 1.0f) { return true; } return false; } private float calculateOverscrollScale(MotionEvent second) { // if at left and overshootDistX is negative or if at right and overshootDistX is // positive then user is not in overscroll state anymore overscroll state. Reset // overscroll values by clearing float overshootDistX = second.getX() - mReachedEdgeCoord.x; if ((mOverscrollState == OVERSCROLL_LEFT_EDGE && overshootDistX < 0) || (mOverscrollState == OVERSCROLL_RIGHT_EDGE && overshootDistX > 0)) { clear(); return -1.0f; } float overshootDistY = second.getY() - mReachedEdgeCoord.y; float overshootDist = (float) (Math.hypot(abs(overshootDistX), abs(overshootDistY))); Rect bounds = new Rect(); mFullScreenMagnificationController.getMagnificationBounds(mDisplayId, bounds); float overShootFraction = overshootDist / (float) bounds.width(); float minDist = 0.05f * bounds.width(); if (mEdgeCooldown && (overshootDist > minDist)) { mEdgeCooldown = false; } float scale = (1 - overShootFraction) * getSensitivityScale(); scale = MathUtils.constrain( /* amount= */ scale, /* low= */ 1.0f, /* high= */ mFullScreenMagnificationController.getPersistedScale( mDisplayId)); return scale; } private float getSensitivityScale() { float magnificationScale = mFullScreenMagnificationController.getPersistedScale(mDisplayId); float sensitivityFactor = 0.0f; if (magnificationScale < 1.7f) { sensitivityFactor = 1.0f; } else if (magnificationScale < 2.0f) { sensitivityFactor = 1.0f; } else if (magnificationScale < 2.2f) { sensitivityFactor = 0.95f; } else if (magnificationScale < 2.5f) { sensitivityFactor = 1.1f; } else if (magnificationScale < 2.7f) { sensitivityFactor = 1.3f; } else if (magnificationScale < 3.0f) { sensitivityFactor = 1.0f; } else { sensitivityFactor = 1.0f; } return magnificationScale * sensitivityFactor; } private void vibrateIfNeeded(MotionEvent event) { if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) || mFullScreenMagnificationController.isAtRightEdge(mDisplayId)) && !mEdgeCooldown) { mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); mReachedEdgeCoord.set(event.getX(), event.getY()); mEdgeCooldown = true; } } @Override public void clear() { mOverscrollState = OVERSCROLL_NONE; mPivotEdge.set(Float.NaN, Float.NaN); mReachedEdgeCoord.set(Float.NaN, Float.NaN); mEdgeCooldown = false; } } }
services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +68 −23 Original line number Diff line number Diff line Loading @@ -589,51 +589,91 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test public void testScroll_zoomedStateAndAtEdge_delegatingState() { goFromStateIdleTo(STATE_ACTIVATED); @FlakyTest public void testScroll_singleHorizontalPanningAndAtEdge_leftEdgeOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, INITIAL_MAGNIFICATION_BOUNDS.top / 2, false, 1); DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, centerY, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF endCoords = new PointF(initCoords.x, initCoords.y); endCoords.offset(swipeMinDistance, 0); allowEventDelegation(); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(swipeMinDistance, 0); swipeAndHold(initCoords, endCoords); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_LEFT_EDGE); assertTrue(isZoomed()); } @Test @FlakyTest public void testScroll_singlePanningAndAtEdge_delegatingState() { public void testScroll_singleHorizontalPanningAndAtEdge_rightEdgeOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, INITIAL_MAGNIFICATION_BOUNDS.top / 2, false, 1); DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.right, centerY, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF endCoords = new PointF(initCoords.x, initCoords.y); endCoords.offset(swipeMinDistance, 0); allowEventDelegation(); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(-swipeMinDistance, 0); swipeAndHold(initCoords, endCoords); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); assertTrue(mMgh.mCurrentState == mMgh.mSinglePanningState); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_RIGHT_EDGE); assertTrue(isZoomed()); } @Test @FlakyTest public void testScroll_singleVerticalPanningAndAtEdge_verticalOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerX = (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, centerX, INITIAL_MAGNIFICATION_BOUNDS.top, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(0, swipeMinDistance); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE); assertTrue(isZoomed()); } @Test public void testScroll_singlePanningAndAtEdge_noOverscroll() { goFromStateIdleTo(STATE_SINGLE_PANNING); float centerY = (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f; mFullScreenMagnificationController.setCenter( DISPLAY_0, INITIAL_MAGNIFICATION_BOUNDS.left, centerY, false, 1); final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; PointF initCoords = new PointF( mFullScreenMagnificationController.getCenterX(DISPLAY_0), mFullScreenMagnificationController.getCenterY(DISPLAY_0)); PointF edgeCoords = new PointF(initCoords.x, initCoords.y); edgeCoords.offset(-swipeMinDistance, 0); swipeAndHold(initCoords, edgeCoords); assertTrue(mMgh.mSinglePanningState.mOverscrollState == mMgh.OVERSCROLL_NONE); assertTrue(isZoomed()); } Loading Loading @@ -1105,6 +1145,11 @@ public class FullScreenMagnificationGestureHandlerTest { send(upEvent()); } private void swipe(PointF start, PointF end) { swipeAndHold(start, end); send(upEvent(end.x, end.y)); } private void swipeAndHold() { send(downEvent()); send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2)); Loading