Merge "Add magnification edge haptic feature." into main

This commit is contained in:
Kiara Patricia
2023-07-26 17:41:06 +00:00
committed by Android (Google) Code Review
5 changed files with 266 additions and 5 deletions

View File

@@ -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;
}

View File

@@ -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{"

View File

@@ -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();
}
}

View File

@@ -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
@@ -633,6 +637,52 @@ public class FullScreenMagnificationGestureHandlerTest {
assertTrue(isZoomed());
}
@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);

View File

@@ -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);
}
}