diff options
| author | 2023-07-26 17:41:06 +0000 | |
|---|---|---|
| committer | 2023-07-26 17:41:06 +0000 | |
| commit | 080276b44c2a4b49163fc147aa3bb5c8684df653 (patch) | |
| tree | f2b626a49c03aa4788d8adf43be8472ab803b179 | |
| parent | ad51b4ca711aa7449b57eeeaa120b2e75d24b250 (diff) | |
| parent | 1d19a4e4d4ccc2881116e541aa9347a2e8c7b1cb (diff) | |
Merge "Add magnification edge haptic feature." into main
5 files changed, 266 insertions, 5 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 8e7d27795c07..f3a540b1c7a5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -40,6 +40,7 @@ import android.view.accessibility.AccessibilityEvent; import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; +import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationPromptController; @@ -654,11 +655,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } else { final Context uiContext = displayContext.createWindowContext( TYPE_MAGNIFICATION_OVERLAY, null /* options */); + FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper = + new FullScreenMagnificationVibrationHelper(uiContext); magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext, mAms.getMagnificationController().getFullScreenMagnificationController(), mAms.getTraceManager(), mAms.getMagnificationController(), detectControlGestures, triggerable, - new WindowMagnificationPromptController(displayContext, mUserId), displayId); + new WindowMagnificationPromptController(displayContext, mUserId), displayId, + fullScreenMagnificationVibrationHelper); } return magnificationGestureHandler; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index fd8babbbd5b1..58b61b36af17 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -155,7 +155,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH boolean detectTripleTap, boolean detectShortcutTrigger, @NonNull WindowMagnificationPromptController promptController, - int displayId) { + int displayId, + FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback); if (DEBUG_ALL) { Log.i(mLogTag, @@ -203,7 +204,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mDetectingState = new DetectingState(context); mViewportDraggingState = new ViewportDraggingState(); mPanningScalingState = new PanningScalingState(context); - mSinglePanningState = new SinglePanningState(context); + mSinglePanningState = new SinglePanningState(context, + fullScreenMagnificationVibrationHelper); setSinglePanningEnabled( context.getResources() .getBoolean(R.bool.config_enable_a11y_magnification_single_panning)); @@ -1334,11 +1336,17 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } final class SinglePanningState extends SimpleOnGestureListener implements State { + + private final GestureDetector mScrollGestureDetector; private MotionEventInfo mEvent; + private final FullScreenMagnificationVibrationHelper + mFullScreenMagnificationVibrationHelper; - SinglePanningState(Context context) { + SinglePanningState(Context context, FullScreenMagnificationVibrationHelper + fullScreenMagnificationVibrationHelper) { mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain()); + mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper; } @Override @@ -1378,10 +1386,20 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) { clear(); transitionTo(mDelegatingState); + vibrateIfNeeded(); } return /* event consumed: */ true; } + private void vibrateIfNeeded() { + if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) + || mFullScreenMagnificationController.isAtRightEdge(mDisplayId))) { + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + } + } + + + @Override public String toString() { return "SinglePanningState{" diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelper.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelper.java new file mode 100644 index 000000000000..37a2eb5c2b75 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelper.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 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 android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserHandle; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Class to encapsulate all the logic to fire a vibration when user reaches the screen's left or + * right edge, when it's in magnification mode. + */ +public class FullScreenMagnificationVibrationHelper { + private static final long VIBRATION_DURATION_MS = 10L; + private static final int VIBRATION_AMPLITUDE = VibrationEffect.MAX_AMPLITUDE / 2; + + @Nullable + private final Vibrator mVibrator; + private final ContentResolver mContentResolver; + private final VibrationEffect mVibrationEffect = VibrationEffect.get( + VibrationEffect.EFFECT_CLICK); + @VisibleForTesting + VibrationEffectSupportedProvider mIsVibrationEffectSupportedProvider; + + public FullScreenMagnificationVibrationHelper(Context context) { + mContentResolver = context.getContentResolver(); + mVibrator = context.getSystemService(Vibrator.class); + mIsVibrationEffectSupportedProvider = + () -> mVibrator != null && mVibrator.areAllEffectsSupported( + VibrationEffect.EFFECT_CLICK) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES; + } + + + void vibrateIfSettingEnabled() { + if (mVibrator != null && mVibrator.hasVibrator() && isEdgeHapticSettingEnabled()) { + if (mIsVibrationEffectSupportedProvider.isVibrationEffectSupported()) { + mVibrator.vibrate(mVibrationEffect); + } else { + mVibrator.vibrate(VibrationEffect.createOneShot(VIBRATION_DURATION_MS, + VIBRATION_AMPLITUDE)); + } + } + } + + private boolean isEdgeHapticSettingEnabled() { + return Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED, + 0, UserHandle.USER_CURRENT) + == 1; + } + + @VisibleForTesting + interface VibrationEffectSupportedProvider { + boolean isVibrationEffectSupported(); + } +} + diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 68bb5a4878c3..4d3bd92551d7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -36,6 +36,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -165,6 +166,8 @@ public class FullScreenMagnificationGestureHandlerTest { WindowMagnificationPromptController mWindowMagnificationPromptController; @Mock AccessibilityTraceManager mMockTraceManager; + @Mock + FullScreenMagnificationVibrationHelper mMockFullScreenMagnificationVibrationHelper; @Rule public final TestableContext mContext = new TestableContext(getInstrumentation().getContext()); @@ -247,7 +250,8 @@ public class FullScreenMagnificationGestureHandlerTest { FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler( mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback, detectTripleTap, detectShortcutTrigger, - mWindowMagnificationPromptController, DISPLAY_0); + mWindowMagnificationPromptController, DISPLAY_0, + mMockFullScreenMagnificationVibrationHelper); h.setSinglePanningEnabled(true); mHandler = new TestHandler(h.mDetectingState, mClock) { @Override @@ -634,6 +638,52 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void testScroll_singleHorizontalPanningAndAtEdge_vibrate() { + goFromStateIdleTo(STATE_SINGLE_PANNING); + mFullScreenMagnificationController.setCenter( + DISPLAY_0, + INITIAL_MAGNIFICATION_BOUNDS.left, + INITIAL_MAGNIFICATION_BOUNDS.top / 2, + 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(); + + swipeAndHold(initCoords, endCoords); + + verify(mMockFullScreenMagnificationVibrationHelper).vibrateIfSettingEnabled(); + } + + @Test + public void testScroll_singleVerticalPanningAndAtEdge_doNotVibrate() { + goFromStateIdleTo(STATE_SINGLE_PANNING); + mFullScreenMagnificationController.setCenter( + DISPLAY_0, + INITIAL_MAGNIFICATION_BOUNDS.left, + 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 endCoords = new PointF(initCoords.x, initCoords.y); + endCoords.offset(0, swipeMinDistance); + allowEventDelegation(); + + swipeAndHold(initCoords, endCoords); + + verify(mMockFullScreenMagnificationVibrationHelper, never()).vibrateIfSettingEnabled(); + } + + @Test public void testShortcutTriggered_invokeShowWindowPromptAction() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelperTest.java new file mode 100644 index 000000000000..85638818f156 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelperTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; +import android.testing.TestableContext; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link FullScreenMagnificationVibrationHelper}. + */ +public class FullScreenMagnificationVibrationHelperTest { + private static final long VIBRATION_DURATION_MS = 10L; + private static final int VIBRATION_AMPLITUDE = VibrationEffect.MAX_AMPLITUDE / 2; + + + @Rule + public final TestableContext mContext = new TestableContext(getInstrumentation().getContext()); + @Mock + Vibrator mMockVibrator; + + private FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext.addMockSystemService(Vibrator.class, mMockVibrator); + mFullScreenMagnificationVibrationHelper = new FullScreenMagnificationVibrationHelper( + mContext); + mFullScreenMagnificationVibrationHelper.mIsVibrationEffectSupportedProvider = () -> true; + } + + @Test + public void edgeHapticSettingEnabled_vibrate() { + setEdgeHapticSettingEnabled(true); + when(mMockVibrator.hasVibrator()).thenReturn(true); + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator).vibrate(any()); + } + + @Test + public void edgeHapticSettingDisabled_doNotVibrate() { + setEdgeHapticSettingEnabled(false); + when(mMockVibrator.hasVibrator()).thenReturn(true); + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator, never()).vibrate(any()); + } + + @Test + public void hasNoVibrator_doNotVibrate() { + setEdgeHapticSettingEnabled(true); + when(mMockVibrator.hasVibrator()).thenReturn(false); + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator, never()).vibrate(any()); + } + + @Test + public void notSupportVibrationEffect_vibrateOneShotEffect() { + setEdgeHapticSettingEnabled(true); + when(mMockVibrator.hasVibrator()).thenReturn(true); + mFullScreenMagnificationVibrationHelper.mIsVibrationEffectSupportedProvider = () -> false; + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator).vibrate(eq(VibrationEffect.createOneShot(VIBRATION_DURATION_MS, + VIBRATION_AMPLITUDE))); + } + + + private boolean setEdgeHapticSettingEnabled(boolean enabled) { + return Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED, + enabled ? 1 : 0); + } +} |