Add accessibility tests for drag and drop
Bug: 26871588
Test: atest AccessibilityDragAndDropTest
Change-Id: I8587811e8920c9a063111a64fc5f589c42568f4f
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index adf2f4f..84981cd 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -74,6 +74,10 @@
android:theme="@android:style/Theme.Dialog"
android:screenOrientation="locked"/>
+ <activity android:label="@string/accessibility_drag_and_drop_test_activity"
+ android:name=".activities.AccessibilityDragAndDropActivity"
+ android:screenOrientation="locked"/>
+
<service android:name=".StubSystemActionsAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
diff --git a/tests/accessibilityservice/res/layout/accessibility_drag_and_drop.xml b/tests/accessibilityservice/res/layout/accessibility_drag_and_drop.xml
new file mode 100644
index 0000000..93d52a3
--- /dev/null
+++ b/tests/accessibilityservice/res/layout/accessibility_drag_and_drop.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2021 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/source"
+ android:text="@string/drag_and_drop_text"
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:layout_margin="60dp"/>
+ <TextView
+ android:id="@+id/target"
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:layout_margin="60dp"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index dd59c0b..fde3d2e 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -195,4 +195,8 @@
<!-- String title of embedded display activity -->
<string name="non_default_display_activity">Non default display activity</string>
+ <!-- String title of the accessibility drag & drop activity -->
+ <string name="accessibility_drag_and_drop_test_activity">Drag and drop</string>
+ <string name="drag_and_drop_text">Dragged text</string>
+
</resources>
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDragAndDropTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDragAndDropTest.java
new file mode 100644
index 0000000..b876dc0
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDragAndDropTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 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.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.cts.activities.AccessibilityDragAndDropActivity;
+import android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.TextView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+public class AccessibilityDragAndDropTest {
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private AccessibilityDragAndDropActivity mActivity;
+ private TextView mSourceView;
+ private UiAutomation.AccessibilityEventFilter mDragStartedFilter =
+ AccessibilityEventFilterUtils.filterWindowContentChangedWithChangeTypes(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_STARTED);
+
+ private ActivityTestRule<AccessibilityDragAndDropActivity> mActivityRule =
+ new ActivityTestRule<>(AccessibilityDragAndDropActivity.class, false, false);
+
+ private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+ new AccessibilityDumpOnFailureRule();
+
+ @Rule
+ public final RuleChain mRuleChain = RuleChain
+ .outerRule(mActivityRule)
+ .around(mDumpOnFailureRule);
+
+ @BeforeClass
+ public static void oneTimeSetup() throws Exception {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation();
+ }
+
+ @AfterClass
+ public static void postTestTearDown() {
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = launchActivityAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation, mActivityRule);
+ mSourceView = mActivity.findViewById(R.id.source);
+ }
+
+ @After
+ public void tearDown() {
+ // Reset system drag state
+ mSourceView.cancelDragAndDrop();
+ }
+
+ @Test
+ public void testStartDrag_eventSentAndActionsUpdated() throws Throwable {
+ AccessibilityEvent startEvent = performActionAndWaitForEvent(mSourceView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_START, mDragStartedFilter);
+ assertNotNull("Did not receive CONTENT_CHANGE_TYPE_DRAG_STARTED", startEvent);
+
+ final AccessibilityNodeInfo sourceNode = getSourceNode();
+ assertNodeAction(sourceNode, AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_CANCEL);
+
+ final AccessibilityNodeInfo targetNode = getTargetNode();
+ assertNodeAction(targetNode, AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_DROP);
+ }
+
+ @Test
+ public void testCancelDrag_eventSentAndActionsUpdated() throws Throwable {
+ performActionAndWaitForEvent(mSourceView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_START,
+ mDragStartedFilter);
+
+ AccessibilityEvent cancelEvent = performActionAndWaitForEvent(mSourceView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_CANCEL,
+ AccessibilityEventFilterUtils.filterWindowContentChangedWithChangeTypes(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_CANCELLED));
+
+ assertNotNull("Did not receive CONTENT_CHANGE_TYPE_DRAG_CANCELLED",
+ cancelEvent);
+
+ final AccessibilityNodeInfo sourceNode = getSourceNode();
+ assertNoNodeAction(sourceNode,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_CANCEL);
+ assertNodeAction(sourceNode, AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_START);
+
+ final AccessibilityNodeInfo targetNode = getTargetNode();
+ assertNoNodeAction(targetNode, AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_DROP);
+ }
+
+ @Test
+ public void testDrop_eventSentAndActionsUpdated() throws Throwable {
+ performActionAndWaitForEvent(mSourceView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_START, mDragStartedFilter);
+ final TextView target = mActivity.findViewById(R.id.target);
+ AccessibilityEvent dropEvent = performActionAndWaitForEvent(target,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_DROP,
+ AccessibilityEventFilterUtils.filterWindowContentChangedWithChangeTypes(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED));
+
+ assertNotNull("Did not receive CONTENT_CHANGE_TYPE_DRAG_DROPPED",
+ dropEvent);
+
+ final AccessibilityNodeInfo targetNode = getTargetNode();
+ assertEquals("Target text was: " + targetNode.getText(), mSourceView.getText(),
+ targetNode.getText());
+ assertNoNodeAction(targetNode, AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_DROP);
+
+ final AccessibilityNodeInfo sourceNode = getSourceNode();
+ assertNoNodeAction(sourceNode,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_CANCEL);
+ assertNodeAction(sourceNode, AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_START);
+ }
+
+ private AccessibilityEvent performActionAndWaitForEvent(View view,
+ AccessibilityNodeInfo.AccessibilityAction action,
+ UiAutomation.AccessibilityEventFilter filter) throws Throwable {
+ AccessibilityEvent awaitedEvent =
+ sUiAutomation.executeAndWaitForEvent(
+ () -> {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.performAccessibilityAction(action.getId(), null);
+ }
+ });
+ },
+ filter,
+ DEFAULT_TIMEOUT_MS);
+ return awaitedEvent;
+ }
+
+ private AccessibilityNodeInfo getSourceNode() {
+ return sUiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "android.accessibilityservice.cts:id/source").get(0);
+ }
+
+ private AccessibilityNodeInfo getTargetNode() {
+ return sUiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "android.accessibilityservice.cts:id/target").get(0);
+ }
+
+ private void assertNoNodeAction(
+ AccessibilityNodeInfo info, AccessibilityNodeInfo.AccessibilityAction action) {
+ assertFalse("Node has action: " + action.toString(),
+ info.getActionList().contains(action));
+ }
+
+ private void assertNodeAction(
+ AccessibilityNodeInfo info, AccessibilityNodeInfo.AccessibilityAction action) {
+ assertTrue("Node does not have action: " + action.toString(),
+ info.getActionList().contains(action));
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityDragAndDropActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityDragAndDropActivity.java
new file mode 100644
index 0000000..ed5cad1
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityDragAndDropActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.activities;
+
+import android.accessibilityservice.cts.R;
+import android.content.ClipData;
+import android.os.Bundle;
+import android.view.DragEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.TextView;
+
+public class AccessibilityDragAndDropActivity extends AccessibilityTestActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_drag_and_drop);
+ final TextView text = findViewById(R.id.source);
+
+ View.AccessibilityDelegate delegate = new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DRAG_START);
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ boolean result = super.performAccessibilityAction(host, action, args);
+ if (action == android.R.id.accessibilityActionDragStart) {
+ final ClipData clipData = ClipData.newPlainText("Text", text.getText());
+ View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder();
+ return host.startDragAndDrop(clipData, shadowBuilder, null,
+ View.DRAG_FLAG_ACCESSIBILITY_ACTION);
+ }
+ return result;
+ }
+ };
+
+ text.setAccessibilityDelegate(delegate);
+ final TextView target = findViewById(R.id.target);
+
+ View.OnDragListener dragListener = new View.OnDragListener() {
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ case DragEvent.ACTION_DRAG_ENTERED:
+ case DragEvent.ACTION_DRAG_LOCATION:
+ case DragEvent.ACTION_DRAG_EXITED:
+ case DragEvent.ACTION_DRAG_ENDED:
+ return true;
+ case DragEvent.ACTION_DROP:
+ ((TextView) v).setText(event.getClipData().getItemAt(0).getText());
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+ }
+ };
+ target.setOnDragListener(dragListener);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
index 7f1cd37..57d1103 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
@@ -38,6 +38,12 @@
return (new AccessibilityEventTypeMatcher(eventType))::matches;
}
+ public static AccessibilityEventFilter filterWindowContentChangedWithChangeTypes(int changes) {
+ return (both(new AccessibilityEventTypeMatcher(
+ AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)).and(
+ new ContentChangesMatcher(changes)))::matches;
+ }
+
public static AccessibilityEventFilter filterWindowsChangedWithChangeTypes(int changes) {
return (both(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED))
.and(new WindowChangesMatcher(changes)))::matches;