Add host side CTS test for cross-app drag and drop
This is a host side replacement for android.dnd.cts.DragAndDropTest.
This should resolve the UIAutomator flakiness problem.
Bug: 28508640
Change-Id: I27b5a2724807e4805560cc0a9c69886129496e9f
diff --git a/OldCtsTestCaseList.mk b/OldCtsTestCaseList.mk
index 68130f3..aff9f3c 100644
--- a/OldCtsTestCaseList.mk
+++ b/OldCtsTestCaseList.mk
@@ -109,6 +109,8 @@
CtsDeviceTaskSwitchingAppA \
CtsDeviceTaskSwitchingAppB \
CtsDeviceTaskSwitchingControl \
+ CtsDragAndDropSourceApp \
+ CtsDragAndDropTargetApp \
CtsExternalServiceService \
CtsHostsideNetworkTestsApp \
CtsHostsideNetworkTestsApp2 \
@@ -266,6 +268,7 @@
CtsAtraceHostTestCases \
CtsCppToolsTestCases \
CtsDevicePolicyManagerTestCases \
+ CtsDragAndDropHostTestCases \
CtsDumpsysHostTestCases \
CtsHostsideNetworkTests \
CtsJdwpSecurityHostTestCases \
diff --git a/hostsidetests/services/windowmanager/Android.mk b/hostsidetests/services/windowmanager/Android.mk
new file mode 100644
index 0000000..eeac27b
--- /dev/null
+++ b/hostsidetests/services/windowmanager/Android.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2015 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# Must match the package name in OldCtsTestCaseList.mk
+LOCAL_MODULE := CtsDragAndDropHostTestCases
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.wm.cts
+
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/services/windowmanager/AndroidTest.xml b/hostsidetests/services/windowmanager/AndroidTest.xml
new file mode 100644
index 0000000..5e305cf
--- /dev/null
+++ b/hostsidetests/services/windowmanager/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<configuration description="Config for CTS drag and drop host test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsDragAndDropSourceApp.apk" />
+ <option name="test-file-name" value="CtsDragAndDropTargetApp.apk" />
+ </target_preparer>
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsDragAndDropHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/services/windowmanager/OldAndroidTest.xml b/hostsidetests/services/windowmanager/OldAndroidTest.xml
new file mode 100644
index 0000000..844bca3
--- /dev/null
+++ b/hostsidetests/services/windowmanager/OldAndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="CTS package preparer for install/uninstall of the apk used as a test operation target">
+ <include name="common-config" />
+ <!-- This will tell tradefed to install the test apk. -->
+ <option name="cts-apk-installer:test-file-name" value="CtsDragAndDropSourceApp.apk" />
+ <option name="cts-apk-installer:test-file-name" value="CtsDragAndDropTargetApp.apk" />
+</configuration>
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/Android.mk b/hostsidetests/services/windowmanager/dndsourceapp/Android.mk
new file mode 100644
index 0000000..1ec751c
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndsourceapp/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropSourceApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml b/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
new file mode 100644
index 0000000..296a979
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.wm.cts.dndsourceapp">
+ <application android:label="CtsDnDSource">
+ <activity android:name="android.wm.cts.dndsourceapp.DragSource">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <provider android:name="android.wm.cts.dndsourceapp.DragSourceContentProvider"
+ android:authorities="android.wm.cts.dndsource.contentprovider"
+ android:grantUriPermissions="true"/>
+ </application>
+</manifest>
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/res/layout/source_activity.xml b/hostsidetests/services/windowmanager/dndsourceapp/res/layout/source_activity.xml
new file mode 100644
index 0000000..673994c
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndsourceapp/res/layout/source_activity.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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">
+ <TextView
+ android:id="@+id/drag_source"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center">
+ </TextView>
+</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
new file mode 100644
index 0000000..ff92656
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 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.wm.cts.dndsourceapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+
+public class DragSource extends Activity{
+ private static final String URI_PREFIX =
+ "content://" + DragSourceContentProvider.AUTHORITY + "/data";
+
+ private static final String MAGIC_VALUE = "42";
+ private static final long TIMEOUT_CANCEL = 150;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ View view = getLayoutInflater().inflate(R.layout.source_activity, null);
+ setContentView(view);
+
+ final Uri plainUri = Uri.parse(URI_PREFIX + "/" + MAGIC_VALUE);
+
+ setUpDragSource("disallow_global", plainUri, 0);
+ setUpDragSource("cancel_soon", plainUri, View.DRAG_FLAG_GLOBAL);
+
+ setUpDragSource("grant_none", plainUri, View.DRAG_FLAG_GLOBAL);
+ setUpDragSource("grant_read", plainUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
+ setUpDragSource("grant_write", plainUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_WRITE);
+ setUpDragSource("grant_read_persistable", plainUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+ View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION);
+
+ final Uri prefixUri = Uri.parse(URI_PREFIX);
+
+ setUpDragSource("grant_read_prefix", prefixUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+ View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION);
+ setUpDragSource("grant_read_noprefix", prefixUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
+ }
+
+ private void setUpDragSource(String mode, final Uri uri, final int flags) {
+ if (!mode.equals(getIntent().getStringExtra("mode"))) {
+ return;
+ }
+ final View source = findViewById(R.id.drag_source);
+ ((TextView) source).setText(mode);
+ source.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ final ClipDescription clipDescription = new ClipDescription("", new String[] {
+ ClipDescription.MIMETYPE_TEXT_URILIST });
+ PersistableBundle extras = new PersistableBundle(1);
+ extras.putString("extraKey", "extraValue");
+ clipDescription.setExtras(extras);
+ final ClipData clipData = new ClipData(clipDescription, new ClipData.Item(uri));
+ v.startDragAndDrop(
+ clipData,
+ new View.DragShadowBuilder(v),
+ null,
+ flags);
+ if (mode.equals("cancel_soon")) {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ v.cancelDragAndDrop();
+ }
+ }, TIMEOUT_CANCEL);
+ }
+ return true;
+ }
+ });
+ }
+}
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
new file mode 100644
index 0000000..06a94aa
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.wm.cts.dndsourceapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class DragSourceContentProvider extends ContentProvider {
+
+ public static final String AUTHORITY = "android.wm.cts.dndsource.contentprovider";
+
+ private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final int URI_DATA = 1;
+
+ static {
+ sMatcher.addURI(AUTHORITY, "data/#", URI_DATA);
+ }
+
+ @Override
+ public boolean onCreate() {
+ return false;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ switch (sMatcher.match(uri)) {
+ case URI_DATA:
+ return new DragSourceCursor(uri.getLastPathSegment());
+ }
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
new file mode 100644
index 0000000..c54df65
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.wm.cts.dndsourceapp;
+
+import android.database.AbstractCursor;
+
+public class DragSourceCursor extends AbstractCursor {
+ private static final String COLUMN_KEY = "key";
+
+ private final String mValue;
+
+ public DragSourceCursor(String value) {
+ mValue = value;
+ }
+
+ @Override
+ public int getCount() {
+ return 1;
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return new String[] {COLUMN_KEY};
+ }
+
+ @Override
+ public String getString(int column) {
+ if (getPosition() != 0) {
+ throw new IllegalArgumentException("Incorrect position: " + getPosition());
+ }
+ if (column != 0) {
+ throw new IllegalArgumentException("Incorrect column: " + column);
+ }
+ return mValue;
+ }
+
+ @Override
+ public short getShort(int column) {
+ return 0;
+ }
+
+ @Override
+ public int getInt(int column) {
+ return 0;
+ }
+
+ @Override
+ public long getLong(int column) {
+ return 0;
+ }
+
+ @Override
+ public float getFloat(int column) {
+ return 0;
+ }
+
+ @Override
+ public double getDouble(int column) {
+ return 0;
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ return false;
+ }
+}
diff --git a/hostsidetests/services/windowmanager/dndtargetapp/Android.mk b/hostsidetests/services/windowmanager/dndtargetapp/Android.mk
new file mode 100644
index 0000000..7dc512c
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndtargetapp/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropTargetApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/windowmanager/dndtargetapp/AndroidManifest.xml b/hostsidetests/services/windowmanager/dndtargetapp/AndroidManifest.xml
new file mode 100644
index 0000000..ed7b9c2
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndtargetapp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.wm.cts.dndtargetapp">
+ <application android:label="CtsDnDTarget">
+ <activity android:name="android.wm.cts.dndtargetapp.DropTarget">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/services/windowmanager/dndtargetapp/res/layout/target_activity.xml b/hostsidetests/services/windowmanager/dndtargetapp/res/layout/target_activity.xml
new file mode 100644
index 0000000..ddcdf33
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndtargetapp/res/layout/target_activity.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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">
+ <TextView
+ android:id="@+id/drag_target"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center">
+ </TextView>
+</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/services/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java b/hostsidetests/services/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
new file mode 100644
index 0000000..904a422
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2016 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.wm.cts.dndtargetapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.util.Log;
+import android.view.DragAndDropPermissions;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.TextView;
+
+public class DropTarget extends Activity {
+ public static final String LOG_TAG = "DropTarget";
+
+ private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+ private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+ private static final String RESULT_KEY_DROP_RESULT = "DROP";
+ private static final String RESULT_KEY_DETAILS = "DETAILS";
+
+ public static final String RESULT_OK = "OK";
+ public static final String RESULT_EXCEPTION = "Exception";
+
+ protected static final String MAGIC_VALUE = "42";
+
+ private TextView mTextView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ View view = getLayoutInflater().inflate(R.layout.target_activity, null);
+ setContentView(view);
+
+ setUpDropTarget("request_none", new OnDragUriReadListener(false));
+ setUpDropTarget("request_read", new OnDragUriReadListener());
+ setUpDropTarget("request_write", new OnDragUriWriteListener());
+ setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
+ setUpDropTarget("request_take_persistable", new OnDragUriTakePersistableListener());
+ }
+
+ private void setUpDropTarget(String mode, OnDragUriListener listener) {
+ if (!mode.equals(getIntent().getStringExtra("mode"))) {
+ return;
+ }
+ mTextView = (TextView)findViewById(R.id.drag_target);
+ mTextView.setText(mode);
+ mTextView.setOnDragListener(listener);
+ }
+
+ private String checkExtraValue(DragEvent event) {
+ PersistableBundle extras = event.getClipDescription().getExtras();
+ if (extras == null) {
+ return "Null";
+ }
+
+ final String value = extras.getString("extraKey");
+ if ("extraValue".equals(value)) {
+ return RESULT_OK;
+ }
+ return value;
+ }
+
+ private void logResult(String key, String value) {
+ Log.i(LOG_TAG, key + "=" + value);
+ mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
+ }
+
+ private abstract class OnDragUriListener implements View.OnDragListener {
+ private final boolean requestPermissions;
+
+ public OnDragUriListener(boolean requestPermissions) {
+ this.requestPermissions = requestPermissions;
+ }
+
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+ logResult(RESULT_KEY_EXTRAS, checkExtraValue(event));
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENTERED:
+ return true;
+
+ case DragEvent.ACTION_DRAG_LOCATION:
+ return true;
+
+ case DragEvent.ACTION_DRAG_EXITED:
+ return true;
+
+ case DragEvent.ACTION_DROP:
+ String result;
+ try {
+ result = processDrop(event, requestPermissions);
+ } catch (Exception e) {
+ result = RESULT_EXCEPTION;
+ logResult(RESULT_KEY_DETAILS, e.getMessage());
+ }
+ logResult(RESULT_KEY_DROP_RESULT, result);
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENDED:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ private String processDrop(DragEvent event, boolean requestPermissions) {
+ final ClipData clipData = event.getClipData();
+ if (clipData == null) {
+ return "Null ClipData";
+ }
+ if (clipData.getItemCount() == 0) {
+ return "Empty ClipData";
+ }
+ ClipData.Item item = clipData.getItemAt(0);
+ if (item == null) {
+ return "Null ClipData.Item";
+ }
+ Uri uri = item.getUri();
+ if (uri == null) {
+ return "Null Uri";
+ }
+
+ DragAndDropPermissions permissions = null;
+ if (requestPermissions) {
+ permissions = requestDragAndDropPermissions(event);
+ if (permissions == null) {
+ return "Null DragAndDropPermissions";
+ }
+ }
+
+ try {
+ return processUri(uri);
+ } finally {
+ if (permissions != null) {
+ permissions.release();
+ }
+ }
+ }
+
+ abstract protected String processUri(Uri uri);
+ }
+
+ private class OnDragUriReadListener extends OnDragUriListener {
+ OnDragUriReadListener(boolean requestPermissions) {
+ super(requestPermissions);
+ }
+
+ OnDragUriReadListener() {
+ super(true);
+ }
+
+ protected String processUri(Uri uri) {
+ return checkQueryResult(uri, MAGIC_VALUE);
+ }
+
+ protected String checkQueryResult(Uri uri, String expectedValue) {
+ Cursor cursor = null;
+ try {
+ cursor = getContentResolver().query(uri, null, null, null, null);
+ if (cursor == null) {
+ return "Null Cursor";
+ }
+ cursor.moveToPosition(0);
+ String value = cursor.getString(0);
+ if (!expectedValue.equals(value)) {
+ return "Wrong value: " + value;
+ }
+ return RESULT_OK;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ private class OnDragUriWriteListener extends OnDragUriListener {
+ OnDragUriWriteListener() {
+ super(true);
+ }
+
+ protected String processUri(Uri uri) {
+ ContentValues values = new ContentValues();
+ values.put("key", 100);
+ getContentResolver().update(uri, values, null, null);
+ return RESULT_OK;
+ }
+ }
+
+ private class OnDragUriReadPrefixListener extends OnDragUriReadListener {
+ @Override
+ protected String processUri(Uri uri) {
+ final String result1 = queryPrefixed(uri, "1");
+ if (!result1.equals(RESULT_OK)) {
+ return result1;
+ }
+ final String result2 = queryPrefixed(uri, "2");
+ if (!result2.equals(RESULT_OK)) {
+ return result2;
+ }
+ return queryPrefixed(uri, "3");
+ }
+
+ private String queryPrefixed(Uri uri, String selector) {
+ final Uri prefixedUri = Uri.parse(uri.toString() + "/" + selector);
+ return checkQueryResult(prefixedUri, selector);
+ }
+ }
+
+ private class OnDragUriTakePersistableListener extends OnDragUriListener {
+ OnDragUriTakePersistableListener() {
+ super(true);
+ }
+
+ @Override
+ protected String processUri(Uri uri) {
+ getContentResolver().takePersistableUriPermission(
+ uri, View.DRAG_FLAG_GLOBAL_URI_READ);
+ getContentResolver().releasePersistableUriPermission(
+ uri, View.DRAG_FLAG_GLOBAL_URI_READ);
+ return RESULT_OK;
+ }
+ }
+}
diff --git a/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java b/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java
new file mode 100644
index 0000000..5dc789a
--- /dev/null
+++ b/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2016 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.wm.cts;
+
+import com.android.tradefed.device.CollectingOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CrossAppDragAndDropTests extends DeviceTestCase {
+ // Constants copied from ActivityManager.StackId. If they are changed there, these must be
+ // updated.
+ /** ID of stack where fullscreen activities are normally launched into. */
+ private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
+
+ /** ID of stack where freeform/resized activities are normally launched into. */
+ private static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
+
+ /** ID of stack that occupies a dedicated region of the screen. */
+ private static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
+
+ private static final String AM_FORCE_STOP = "am force-stop ";
+ private static final String AM_MOVE_TASK = "am stack movetask ";
+ private static final String AM_START_N = "am start -n ";
+ private static final String AM_STACK_LIST = "am stack list";
+ private static final String INPUT_MOUSE_SWIPE = "input mouse swipe ";
+
+ private static final String TASK_ID_PREFIX = "taskId";
+
+ private static final int SWIPE_DURATION_MS = 500;
+
+ private static final String SOURCE_PACKAGE_NAME = "android.wm.cts.dndsourceapp";
+ private static final String TARGET_PACKAGE_NAME = "android.wm.cts.dndtargetapp";
+
+ private static final String SOURCE_ACTIVITY_NAME = "DragSource";
+ private static final String TARGET_ACTIVITY_NAME = "DropTarget";
+
+ private static final String DISALLOW_GLOBAL = "disallow_global";
+ private static final String CANCEL_SOON = "cancel_soon";
+ private static final String GRANT_NONE = "grant_none";
+ private static final String GRANT_READ = "grant_read";
+ private static final String GRANT_WRITE = "grant_write";
+ private static final String GRANT_READ_PREFIX = "grant_read_prefix";
+ private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
+ private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
+
+ private static final String REQUEST_NONE = "request_none";
+ private static final String REQUEST_READ = "request_read";
+ private static final String REQUEST_READ_NESTED = "request_read_nested";
+ private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
+ private static final String REQUEST_WRITE = "request_write";
+
+ private static final String TARGET_LOG_TAG = "DropTarget";
+
+ private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+ private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+ private static final String RESULT_KEY_DROP_RESULT = "DROP";
+
+ private static final String RESULT_OK = "OK";
+ private static final String RESULT_EXCEPTION = "Exception";
+ private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
+
+ private ITestDevice mDevice;
+
+ private Map<String, String> mResults;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mDevice = getDevice();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mDevice.executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
+ mDevice.executeShellCommand(AM_FORCE_STOP + TARGET_PACKAGE_NAME);
+ }
+
+ private String adbShell(String command) throws DeviceNotAvailableException {
+ return mDevice.executeShellCommand(command);
+ }
+
+ private void clearLogs() throws DeviceNotAvailableException {
+ adbShell("logcat -c");
+ }
+
+ private String getStartCommand(String componentName, String modeExtra) {
+ return AM_START_N + componentName + " -e mode " + modeExtra;
+ }
+
+ private String getMoveTaskCommand(int taskId, int stackId) throws Exception {
+ return AM_MOVE_TASK + taskId + " " + stackId + " true";
+ }
+
+ private String getComponentName(String packageName, String activityName) {
+ return packageName + "/" + packageName + "." + activityName;
+ }
+
+ private void launchDockedActivity(String packageName, String activityName, String mode)
+ throws Exception {
+ clearLogs();
+ final String componentName = getComponentName(packageName, activityName);
+ adbShell(getStartCommand(componentName, mode));
+ final int taskId = getActivityTaskId(componentName);
+ adbShell(getMoveTaskCommand(taskId, DOCKED_STACK_ID));
+ waitForResume(packageName, activityName);
+ }
+
+ private void launchFreeformActivity(String packageName, String activityName, String mode)
+ throws Exception {
+ clearLogs();
+ final String componentName = getComponentName(packageName, activityName);
+ adbShell(getStartCommand(componentName, mode) + " --stack " + FREEFORM_WORKSPACE_STACK_ID);
+ waitForResume(packageName, activityName);
+ }
+
+ private void waitForResume(String packageName, String activityName) throws Exception {
+ final String fullActivityName = packageName + "." + activityName;
+ int retryCount = 3;
+ do {
+ String logs = adbShell("logcat -d -b events");
+ for (String line : logs.split("\\n")) {
+ if(line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
+ return;
+ }
+ }
+ } while (retryCount-- > 0);
+
+ throw new Exception(fullActivityName + " has failed to start");
+ }
+
+ private void injectInput(Point from, Point to, int durationMs) throws Exception {
+ adbShell(INPUT_MOUSE_SWIPE + from.x + " " + from.y + " " + to.x + " " + to.y + " " +
+ durationMs);
+ }
+
+ static class Point {
+ public int x, y;
+
+ public Point(int _x, int _y) {
+ x=_x;
+ y=_y;
+ }
+
+ public Point() {}
+ }
+
+ private String findTaskInfo(String name) throws Exception {
+ CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
+ mDevice.executeShellCommand(AM_STACK_LIST, outputReceiver);
+ final String output = outputReceiver.getOutput();
+ for (String line : output.split("\\n")) {
+ if (line.contains(name)) {
+ return line;
+ }
+ }
+ return "";
+ }
+
+ private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
+ final String taskInfo = findTaskInfo(name);
+ final String[] sections = taskInfo.split("\\[");
+ if (sections.length > 2) {
+ try {
+ parsePoint(sections[1], from);
+ parsePoint(sections[2], to);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private int getActivityTaskId(String name) throws Exception {
+ final String taskInfo = findTaskInfo(name);
+ for (String word : taskInfo.split("\\s+")) {
+ if (word.startsWith(TASK_ID_PREFIX)) {
+ final String withColon = word.split("=")[1];
+ return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
+ }
+ }
+ return -1;
+ }
+
+ private Point getWindowCenter(String name) throws Exception {
+ Point p1 = new Point();
+ Point p2 = new Point();
+ if (getWindowBounds(name, p1, p2)) {
+ return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
+ }
+ return null;
+ }
+
+ private void parsePoint(String string, Point point) {
+ final String[] parts = string.split("[,|\\]]");
+ point.x = Integer.parseInt(parts[0]);
+ point.y = Integer.parseInt(parts[1]);
+ }
+
+ private Map<String, String> getLogResults(String className) throws Exception {
+ int retryCount = 3;
+ Map<String, String> output = new HashMap<String, String>();
+ do {
+
+ String logs = adbShell("logcat -v brief -d " + className + ":I" + " *:S");
+ for (String line : logs.split("\\n")) {
+ if (line.startsWith("I/" + className)) {
+ String payload = line.split(":")[1].trim();
+ final String[] split = payload.split("=");
+ if (split.length > 1) {
+ output.put(split[0], split[1]);
+ }
+ }
+ }
+ if (output.containsKey(RESULT_KEY_DROP_RESULT)) {
+ return output;
+ }
+ } while (retryCount-- > 0);
+ return output;
+ }
+
+ private void doTestDragAndDrop(String sourceMode, String targetMode, String expectedDropResult)
+ throws Exception {
+
+ launchDockedActivity(SOURCE_PACKAGE_NAME, SOURCE_ACTIVITY_NAME, sourceMode);
+ launchFreeformActivity(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME, targetMode);
+
+ clearLogs();
+
+ injectInput(
+ getWindowCenter(getComponentName(SOURCE_PACKAGE_NAME, SOURCE_ACTIVITY_NAME)),
+ getWindowCenter(getComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME)),
+ SWIPE_DURATION_MS);
+
+ mResults = getLogResults(TARGET_LOG_TAG);
+ assertResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
+ }
+
+
+ private void assertResult(String resultKey, String expectedResult) {
+ if (expectedResult == null) {
+ if (mResults.containsKey(resultKey)) {
+ fail("Unexpected " + resultKey + "=" + mResults.get(resultKey));
+ }
+ } else {
+ assertTrue("Missing " + resultKey, mResults.containsKey(resultKey));
+ assertEquals(expectedResult, mResults.get(resultKey));
+ }
+ }
+
+ public void testCancelSoon() throws Exception {
+ doTestDragAndDrop(CANCEL_SOON, REQUEST_NONE, null);
+ assertResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+ assertResult(RESULT_KEY_EXTRAS, RESULT_OK);
+ }
+
+ public void testDisallowGlobal() throws Exception {
+ doTestDragAndDrop(DISALLOW_GLOBAL, REQUEST_NONE, null);
+ assertResult(RESULT_KEY_DRAG_STARTED, null);
+ }
+
+ public void testGrantNoneRequestNone() throws Exception {
+ doTestDragAndDrop(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
+ assertResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+ assertResult(RESULT_KEY_EXTRAS, RESULT_OK);
+ }
+
+ public void testGrantNoneRequestRead() throws Exception {
+ doTestDragAndDrop(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
+ }
+
+ public void testGrantNoneRequestWrite() throws Exception {
+ doTestDragAndDrop(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
+ }
+
+ public void testGrantReadRequestNone() throws Exception {
+ doTestDragAndDrop(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
+ }
+
+ public void testGrantReadRequestRead() throws Exception {
+ doTestDragAndDrop(GRANT_READ, REQUEST_READ, RESULT_OK);
+ }
+
+ public void testGrantReadRequestWrite() throws Exception {
+ doTestDragAndDrop(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
+ }
+
+ public void testGrantReadNoPrefixRequestReadNested() throws Exception {
+ doTestDragAndDrop(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
+ }
+
+ public void testGrantReadPrefixRequestReadNested() throws Exception {
+ doTestDragAndDrop(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
+ }
+
+ public void testGrantPersistableRequestTakePersistable() throws Exception {
+ doTestDragAndDrop(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
+ }
+
+ public void testGrantReadRequestTakePersistable() throws Exception {
+ doTestDragAndDrop(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
+ }
+
+ public void testGrantWriteRequestNone() throws Exception {
+ doTestDragAndDrop(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
+ }
+
+ public void testGrantWriteRequestRead() throws Exception {
+ doTestDragAndDrop(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
+ }
+
+ public void testGrantWriteRequestWrite() throws Exception {
+ doTestDragAndDrop(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
+ }
+}