blob: 7ffb23d5f776f57c3852f14585a8ca4aa47daf58 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.accessibilityservice.cts;
import static android.accessibilityservice.cts.utils.AsyncUtils.await;
import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_DOWN;
import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_UP;
import static android.accessibilityservice.cts.utils.GestureUtils.add;
import static android.accessibilityservice.cts.utils.GestureUtils.click;
import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
import static android.accessibilityservice.cts.utils.GestureUtils.doubleTapAndHold;
import static android.accessibilityservice.cts.utils.GestureUtils.isRawAtPoint;
import static android.accessibilityservice.cts.utils.GestureUtils.multiTap;
import static android.accessibilityservice.cts.utils.GestureUtils.secondFingerMultiTap;
import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.MatcherAssert.assertThat;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibility.cts.common.ShellCommandBuilder;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
import android.accessibilityservice.cts.utils.EventCapturingClickListener;
import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.PointF;
import android.graphics.Region;
import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.List;
/**
* A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
* appropriate hover and/or touch events followed by the appropriate accessibility events. Some
* tests will then check for events from the view.
*/
@RunWith(AndroidJUnit4.class)
@AppModeFull
public class TouchExplorerTest {
// Constants
private static final float GESTURE_LENGTH_MMS = 10.0f;
private TouchExplorationStubAccessibilityService mService;
private Instrumentation mInstrumentation;
private UiAutomation mUiAutomation;
private boolean mHasTouchscreen;
private boolean mScreenBigEnough;
private long mSwipeTimeMillis;
private String mEnabledServices;
private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
private EventCapturingLongClickListener mLongClickListener =
new EventCapturingLongClickListener();
private ActivityTestRule<GestureDispatchActivity> mActivityRule =
new ActivityTestRule<>(GestureDispatchActivity.class, false);
private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
mServiceRule =
new InstrumentedAccessibilityServiceTestRule<>(
TouchExplorationStubAccessibilityService.class, false);
private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
new AccessibilityDumpOnFailureRule();
@Rule
public final RuleChain mRuleChain =
RuleChain.outerRule(mActivityRule).around(mServiceRule).around(mDumpOnFailureRule);
PointF mTapLocation; // Center of activity. Gestures all start from around this point.
float mSwipeDistance;
View mView;
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
// Save enabled accessibility services before disabling them so they can be re-enabled after
// the test.
mEnabledServices = Settings.Secure.getString(
mInstrumentation.getContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
// Disable all services before enabling Accessibility service to prevent flakiness
// that depends on which services are enabled.
InstrumentedAccessibilityService.disableAllServices();
PackageManager pm = mInstrumentation.getContext().getPackageManager();
mHasTouchscreen =
pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
|| pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
// Find window size, check that it is big enough for gestures.
// Gestures will start in the center of the window, so we need enough horiz/vert space.
mService = mServiceRule.enableService();
// To prevent a deadlock, we disable UiAutomation while another a11y service is running.
mInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES).destroy();
mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
WindowManager windowManager =
(WindowManager)
mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
final DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(metrics);
mScreenBigEnough =
mView.getWidth() / 2
> TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MMS, metrics);
if (!mHasTouchscreen || !mScreenBigEnough) return;
mView.setOnHoverListener(mHoverListener);
mView.setOnTouchListener(mTouchListener);
mInstrumentation.runOnMainSync(
() -> {
int[] viewLocation = new int[2];
mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
final int midX = mView.getWidth() / 2;
final int midY = mView.getHeight() / 2;
mView.getLocationOnScreen(viewLocation);
mTapLocation = new PointF(viewLocation[0] + midX, viewLocation[1] + midY);
mSwipeDistance = mView.getWidth() / 4;
// This must be slower than 10mm per 150ms to be detected as touch exploration.
final double swipeDistanceMm = mSwipeDistance / metrics.xdpi * 25.4;
mSwipeTimeMillis = (long) swipeDistanceMm * 20;
mView.setOnClickListener(mClickListener);
mView.setOnLongClickListener(mLongClickListener);
});
}
@After
public void postTestTearDown() {
ShellCommandBuilder.create(mInstrumentation)
.putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mEnabledServices)
.run();
}
/** Test a slow swipe which should initiate touch exploration. */
@Test
@AppModeFull
public void testSlowSwipe_initiatesTouchExploration() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
PointF endPoint = add(mTapLocation, mSwipeDistance, 0);
dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), mSwipeTimeMillis));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
TYPE_TOUCH_INTERACTION_END);
}
/** Test a fast swipe which should not initiate touch exploration. */
@Test
@AppModeFull
public void testFastSwipe_doesNotInitiateTouchExploration() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
PointF endPoint = add(mTapLocation, mSwipeDistance, 0);
dispatch(swipe(mTapLocation, endPoint));
mHoverListener.assertNonePropagated();
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_GESTURE_DETECTION_START,
TYPE_GESTURE_DETECTION_END,
TYPE_TOUCH_INTERACTION_END);
List<MotionEvent> motionEvents = getMotionEventsForLastGesture();
assertThat(motionEvents.get(0), both(IS_ACTION_DOWN).and(isRawAtPoint(mTapLocation, 1.0f)));
assertThat(
motionEvents.get(motionEvents.size() - 1),
both(IS_ACTION_UP).and(isRawAtPoint(endPoint, 1.0f)));
}
/**
* Test a two finger drag. TouchExplorer would perform a drag gesture when two fingers moving in
* the same direction.
*/
@Test
@AppModeFull
public void testTwoFingerDrag_dispatchesEventsBetweenFingers() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
// A two point moving that are in the same direction can perform a drag gesture by
// TouchExplorer while one point moving can not perform a drag gesture. We use two swipes
// to emulate a two finger drag gesture.
final int twoFingerOffset = (int) mSwipeDistance;
final PointF dragStart = mTapLocation;
final PointF dragEnd = add(dragStart, 0, mSwipeDistance);
final PointF finger1Start = add(dragStart, twoFingerOffset, 0);
final PointF finger1End = add(finger1Start, 0, mSwipeDistance);
final PointF finger2Start = add(dragStart, -twoFingerOffset, 0);
final PointF finger2End = add(finger2Start, 0, mSwipeDistance);
dispatch(
swipe(finger1Start, finger1End, mSwipeTimeMillis),
swipe(finger2Start, finger2End, mSwipeTimeMillis));
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
}
/** Test a basic single tap which should initiate touch exploration. */
@Test
@AppModeFull
public void testSingleTap_initiatesTouchExploration() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
dispatch(click(mTapLocation));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
TYPE_TOUCH_INTERACTION_END);
}
/**
* Test the case where we execute a "sloppy" double tap, meaning that the second tap isn't
* exactly in the same location as the first but still within tolerances. It should behave the
* same as a standard double tap. Note that this test does not request that double tap be
* dispatched to the accessibility service, meaning that it will be handled by the framework and
* the view will be clicked.
*/
@Test
@AppModeFull
public void testSloppyDoubleTapAccessibilityFocus_performsClick() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
syncAccessibilityFocusToInputFocus();
int slop = ViewConfiguration.get(mInstrumentation.getContext()).getScaledDoubleTapSlop();
dispatch(multiTap(mTapLocation, 2, slop));
mHoverListener.assertNonePropagated();
// The click should not be delivered via touch events in this case.
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_INTERACTION_END,
TYPE_VIEW_CLICKED);
mClickListener.assertClicked(mView);
}
/**
* Test the case where we want to click on the item that has accessibility focus by using
* AccessibilityNodeInfo.performAction. Note that this test does not request that double tap be
* dispatched to the accessibility service, meaning that it will be handled by the framework and
* the view will be clicked.
*/
@Test
@AppModeFull
public void testDoubleTapAccessibilityFocus_performsClick() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
syncAccessibilityFocusToInputFocus();
dispatch(doubleTap(mTapLocation));
mHoverListener.assertNonePropagated();
// The click should not be delivered via touch events in this case.
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_INTERACTION_END,
TYPE_VIEW_CLICKED);
mClickListener.assertClicked(mView);
}
/**
* Test the case where we double tap but there is no accessibility focus. Nothing should happen.
*/
@Test
@AppModeFull
public void testDoubleTapNoFocus_doesNotPerformClick() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
dispatch(doubleTap(mTapLocation));
mHoverListener.assertNonePropagated();
mTouchListener.assertNonePropagated();
mService.assertPropagated(TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
mClickListener.assertNoneClicked();
List<MotionEvent> motionEvents = getMotionEventsForLastGesture();
assertThat(motionEvents.get(0), both(IS_ACTION_DOWN).and(isRawAtPoint(mTapLocation, 1.0f)));
assertThat(motionEvents.get(1), both(IS_ACTION_UP).and(isRawAtPoint(mTapLocation, 1.0f)));
assertThat(motionEvents.get(2), both(IS_ACTION_DOWN).and(isRawAtPoint(mTapLocation, 1.0f)));
assertThat(motionEvents.get(3), both(IS_ACTION_UP).and(isRawAtPoint(mTapLocation, 1.0f)));
}
/**
* Test the case where we double tap and hold but there is no accessibility focus. Nothing
* should happen.
*/
@Test
@AppModeFull
public void testDoubleTapAndHoldNoFocus_doesNotPerformLongClick() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
dispatch(doubleTap(mTapLocation));
mHoverListener.assertNonePropagated();
mTouchListener.assertNonePropagated();
mService.assertPropagated(TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
mLongClickListener.assertNoneLongClicked();
}
/**
* Test the case where we want to double tap using a second finger while the first finger is
* touch exploring.
*/
@Test
@AppModeFull
public void testSecondFingerDoubleTapTouchExploring_performsClick() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
syncAccessibilityFocusToInputFocus();
// hold the first finger for long enough to trigger touch exploration before double-tapping.
// Touch exploration is triggered after the double tap timeout.
dispatch(
secondFingerMultiTap(
mTapLocation,
add(mTapLocation, mSwipeDistance, 0),
2,
ViewConfiguration.getDoubleTapTimeout() + 50));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
TYPE_TOUCH_INTERACTION_END,
TYPE_VIEW_CLICKED);
mClickListener.assertClicked(mView);
}
/**
* Test the case where we double tap and no item has accessibility focus, so TouchExplorer sends
* touch events to the last touch-explored coordinates to simulate a click.
*/
@Test
@AppModeFull
public void testDoubleTapNoAccessibilityFocus_sendsTouchEvents() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
// Do a single tap so there is a valid last touch-explored location.
dispatch(click(mTapLocation));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
// We don't really care about these events but we need to make sure all the events we want
// to clear have arrived before we clear them.
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
dispatch(doubleTap(mTapLocation));
mHoverListener.assertNonePropagated();
// The click gets delivered as a series of touch events.
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
mClickListener.assertClicked(mView);
}
/**
* Test the case where we double tap and hold and no item has accessibility focus, so
* TouchExplorer sends touch events to the last touch-explored coordinates to simulate a long
* click.
*/
@Test
@AppModeFull
public void testDoubleTapAndHoldNoAccessibilityFocus_sendsTouchEvents() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
// Do a single tap so there is a valid last touch-explored location.
dispatch(click(mTapLocation));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
// We don't really care about these events but we need to make sure all the events we want
// to clear have arrived before we clear them.
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
dispatch(doubleTapAndHold(mTapLocation));
mHoverListener.assertNonePropagated();
// The click gets delivered as a series of touch events.
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START, TYPE_VIEW_LONG_CLICKED, TYPE_TOUCH_INTERACTION_END);
mLongClickListener.assertLongClicked(mView);
}
/**
* Test the case where we want to double tap using a second finger without triggering touch
* exploration.
*/
@Test
@AppModeFull
public void testSecondFingerDoubleTapNotTouchExploring_performsClick() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
syncAccessibilityFocusToInputFocus();
// Hold the first finger for less than the double tap timeout which will not trigger touch
// exploration.
// Touch exploration is triggered after the double tap timeout.
dispatch(
secondFingerMultiTap(
mTapLocation,
add(mTapLocation, mSwipeDistance, 0),
2,
ViewConfiguration.getDoubleTapTimeout() / 3));
mHoverListener.assertNonePropagated();
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_INTERACTION_END,
TYPE_VIEW_CLICKED);
mClickListener.assertClicked(mView);
}
/**
* This method tests a three-finger swipe. The gesture will be delegated to the view as-is. This
* is distinct from dragging, where two fingers are delegated to the view as one finger. Note
* that because multi-finger gestures are disabled this gesture will not be handled by the
* gesture detector.
*/
@Test
@AppModeFull
public void testThreeFingerMovement_shouldDelegate() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
// Move three fingers down the screen slowly.
PointF finger1Start = add(mTapLocation, -mSwipeDistance, 0);
PointF finger1End = add(mTapLocation, -mSwipeDistance, mSwipeDistance);
PointF finger2Start = mTapLocation;
PointF finger2End = add(mTapLocation, 0, mSwipeDistance);
PointF finger3Start = add(mTapLocation, mSwipeDistance, 0);
PointF finger3End = add(mTapLocation, mSwipeDistance, mSwipeDistance);
StrokeDescription swipe1 = swipe(finger1Start, finger1End, mSwipeTimeMillis);
StrokeDescription swipe2 = swipe(finger2Start, finger2End, mSwipeTimeMillis);
StrokeDescription swipe3 = swipe(finger3Start, finger3End, mSwipeTimeMillis);
dispatch(swipe1, swipe2, swipe3);
mHoverListener.assertNonePropagated();
mTouchListener.assertPropagated(
ACTION_DOWN,
ACTION_POINTER_DOWN,
ACTION_POINTER_DOWN,
ACTION_MOVE,
ACTION_POINTER_UP,
ACTION_POINTER_UP,
ACTION_UP);
}
/**
* This method tests the case where two fingers are moving independently. The gesture will be
* delegated to the view as-is. This is distinct from dragging, where two fingers are delegated
* to the view as one finger.
*/
@Test
@AppModeFull
public void testTwoFingersMovingIndependently_shouldDelegate() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
// Move two fingers towards eacher slowly.
PointF finger1Start = add(mTapLocation, -mSwipeDistance, 0);
PointF finger1End = add(mTapLocation, -10, 0);
StrokeDescription swipe1 = swipe(finger1Start, finger1End, mSwipeTimeMillis);
PointF finger2Start = add(mTapLocation, mSwipeDistance, 0);
PointF finger2End = add(mTapLocation, 10, 0);
StrokeDescription swipe2 = swipe(finger2Start, finger2End, mSwipeTimeMillis);
dispatch(swipe1, swipe2);
mHoverListener.assertNonePropagated();
mTouchListener.assertPropagated(
ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE, ACTION_POINTER_UP, ACTION_UP);
}
/**
* Test the gesture detection passthrough by performing a fast swipe in the passthrough region.
* It should bypass the gesture detector entirely.
*/
@Test
@AppModeFull
public void testGestureDetectionPassthrough_initiatesTouchExploration() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
setRightSideOfActivityWindowGestureDetectionPassthrough();
// Swipe in the passthrough region. This should generate hover events.
dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
// Swipe starting inside the passthrough region but ending outside of it. This should still
// behave as a passthrough interaction.
dispatch(swipe(mTapLocation, add(mTapLocation, -mSwipeDistance, 0)));
mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_TOUCH_EXPLORATION_GESTURE_START,
TYPE_TOUCH_EXPLORATION_GESTURE_END,
TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
// Swipe outside the passthrough region. This should not generate hover events.
dispatch(swipe(add(mTapLocation, -1, 0), add(mTapLocation, -mSwipeDistance, 0)));
mHoverListener.assertNonePropagated();
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_GESTURE_DETECTION_START,
TYPE_GESTURE_DETECTION_END,
TYPE_TOUCH_INTERACTION_END);
mService.clearEvents();
// There should be no touch events in this test.
mTouchListener.assertNonePropagated();
clearPassthroughRegions();
}
/**
* Test the touch exploration passthrough by performing a fast swipe in the passthrough region.
* It should generate touch events.
*/
@Test
@AppModeFull
public void testTouchExplorationPassthrough_sendsTouchEvents() {
if (!mHasTouchscreen || !mScreenBigEnough) return;
setRightSideOfActivityWindowTouchExplorationPassthrough();
// Swipe in the passthrough region. This should generate touch events.
dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
// We still want accessibility events to tell us when the gesture starts and ends.
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
mService.clearEvents();
// Swipe starting inside the passthrough region but ending outside of it. This should still
// behave as a passthrough interaction.
dispatch(swipe(mTapLocation, add(mTapLocation, -mSwipeDistance, 0)));
mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
mService.clearEvents();
// Swipe outside the passthrough region. This should not generate touch events.
dispatch(swipe(add(mTapLocation, -1, 0), add(mTapLocation, -mSwipeDistance, 0)));
mTouchListener.assertNonePropagated();
mService.assertPropagated(
TYPE_TOUCH_INTERACTION_START,
TYPE_GESTURE_DETECTION_START,
TYPE_GESTURE_DETECTION_END,
TYPE_TOUCH_INTERACTION_END);
// There should be no hover events in this test.
mHoverListener.assertNonePropagated();
clearPassthroughRegions();
}
public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
GestureDescription.Builder builder =
new GestureDescription.Builder().addStroke(firstStroke);
for (StrokeDescription stroke : rest) {
builder.addStroke(stroke);
}
dispatch(builder.build());
}
public void dispatch(GestureDescription gesture) {
await(dispatchGesture(mService, gesture));
}
/** Set the accessibility focus to the element that has input focus. */
private void syncAccessibilityFocusToInputFocus() {
mService.runOnServiceSync(
() -> {
AccessibilityNodeInfo focus = mService.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
focus.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
focus.recycle();
});
mService.waitForAccessibilityFocus();
}
private void setRightSideOfActivityWindowGestureDetectionPassthrough() {
Region region = getRightSideOfActivityWindowRegion();
mService.runOnServiceSync(
() -> {
mService.setGestureDetectionPassthroughRegion(Display.DEFAULT_DISPLAY, region);
});
}
private void setRightSideOfActivityWindowTouchExplorationPassthrough() {
Region region = getRightSideOfActivityWindowRegion();
mService.runOnServiceSync(
() -> {
mService.setTouchExplorationPassthroughRegion(Display.DEFAULT_DISPLAY, region);
});
}
private void clearPassthroughRegions() {
mService.runOnServiceSync(
() -> {
mService.setGestureDetectionPassthroughRegion(
Display.DEFAULT_DISPLAY, new Region());
mService.setTouchExplorationPassthroughRegion(
Display.DEFAULT_DISPLAY, new Region());
});
}
private Region getRightSideOfActivityWindowRegion() {
int[] viewLocation = new int[2];
mView.getLocationOnScreen(viewLocation);
int top = viewLocation[1];
int left = viewLocation[0] + mView.getWidth() / 2;
int right = viewLocation[0] + mView.getWidth();
int bottom = viewLocation[1] + mView.getHeight();
Region region = new Region(left, top, right, bottom);
return region;
}
private List<MotionEvent> getMotionEventsForLastGesture() {
return mService.getGestureInfo(mService.getGestureInfoSize() - 1).getMotionEvents();
}
}