Add CTS test for VirtualDeviceManager
Test creation of virtual device, activity blocking, launching pending
intent etc.
Bug: 204606917
Bug: 202768910
Bug: 205811272
Test: atest CtsVirtualDevicesTestCases
Change-Id: I1b45757906579eb496321495835542a87993016f
diff --git a/tests/tests/virtualdevice/Android.bp b/tests/tests/virtualdevice/Android.bp
new file mode 100644
index 0000000..16fed4e
--- /dev/null
+++ b/tests/tests/virtualdevice/Android.bp
@@ -0,0 +1,55 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsVirtualDevicesTestCases",
+
+ defaults: ["cts_defaults"],
+
+ platform_apis: true,
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+
+ static_libs: [
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "junit",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ ":CtsVirtualDevicesTestAidl",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+filegroup {
+ name: "CtsVirtualDevicesTestAidl",
+ srcs: [
+ "aidl/**/*.aidl",
+ ],
+}
diff --git a/tests/tests/virtualdevice/AndroidManifest.xml b/tests/tests/virtualdevice/AndroidManifest.xml
new file mode 100644
index 0000000..bc6d1c4
--- /dev/null
+++ b/tests/tests/virtualdevice/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.virtualdevice.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".util.EmptyActivity"
+ android:allowEmbedded="true"
+ android:exported="false" />
+ </application>
+
+ <uses-feature android:name="android.software.companion_device_setup" />
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.virtualdevice.cts"
+ android:label="CTS tests of android.companion.virtual">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/virtualdevice/AndroidTest.xml b/tests/tests/virtualdevice/AndroidTest.xml
new file mode 100644
index 0000000..c7db7f5
--- /dev/null
+++ b/tests/tests/virtualdevice/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Configuration for Virtual Device Manager Tests">
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <option name="not-shardable" value="true" />
+ <option name="test-suite-tag" value="cts" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsVirtualDevicesTestCases.apk" />
+ <option name="test-file-name" value="CtsVirtualDeviceStreamedTestApp.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.virtualdevice.cts" />
+ <option name="runtime-hint" value="1s" />
+ </test>
+</configuration>
diff --git a/tests/tests/virtualdevice/aidl/android/virtualdevice/cts/IStreamedTestApp.aidl b/tests/tests/virtualdevice/aidl/android/virtualdevice/cts/IStreamedTestApp.aidl
new file mode 100644
index 0000000..67d25b7
--- /dev/null
+++ b/tests/tests/virtualdevice/aidl/android/virtualdevice/cts/IStreamedTestApp.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.cts;
+
+import android.app.PendingIntent;
+import android.os.ResultReceiver;
+
+interface IStreamedTestApp {
+ PendingIntent createActivityPendingIntent(in ResultReceiver resultReceiver);
+ PendingIntent createServicePendingIntent(boolean trampoline, in ResultReceiver resultReceiver);
+}
\ No newline at end of file
diff --git a/tests/tests/virtualdevice/app/Android.bp b/tests/tests/virtualdevice/app/Android.bp
new file mode 100644
index 0000000..3fd6bd3
--- /dev/null
+++ b/tests/tests/virtualdevice/app/Android.bp
@@ -0,0 +1,32 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsVirtualDeviceStreamedTestApp",
+ srcs: [
+ "src/**/*.java",
+ "//cts/tests/tests/virtualdevice:CtsVirtualDevicesTestAidl",
+ ],
+ manifest: "AndroidManifest.xml",
+ defaults: ["cts_defaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
diff --git a/tests/tests/virtualdevice/app/AndroidManifest.xml b/tests/tests/virtualdevice/app/AndroidManifest.xml
new file mode 100644
index 0000000..7293ff72
--- /dev/null
+++ b/tests/tests/virtualdevice/app/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.virtualdevice.streamedtestapp">
+
+ <application>
+ <activity android:name=".MainActivity"
+ android:allowEmbedded="true"
+ android:exported="true" />
+
+ <activity android:name=".NoEmbedActivity"
+ android:exported="true" />
+
+ <activity android:name=".CannotDisplayOnRemoteActivity"
+ android:allowEmbedded="true"
+ android:canDisplayOnRemoteDevices="false"
+ android:exported="true" />
+
+ <service android:name=".StreamedAppService"
+ android:exported="true" />
+ </application>
+
+</manifest>
+
diff --git a/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/CannotDisplayOnRemoteActivity.java b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/CannotDisplayOnRemoteActivity.java
new file mode 100644
index 0000000..9dbcae3
--- /dev/null
+++ b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/CannotDisplayOnRemoteActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.streamedtestapp;
+
+/**
+ * Same as MainActivity, but with canDisplayOnRemoteDevices="false" in the manifest.
+ */
+public class CannotDisplayOnRemoteActivity extends MainActivity {
+}
diff --git a/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/MainActivity.java b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/MainActivity.java
new file mode 100644
index 0000000..85d9ec1
--- /dev/null
+++ b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/MainActivity.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.streamedtestapp;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+/**
+ * Test activity to be streamed in the virtual device.
+ */
+public class MainActivity extends Activity {
+
+ private static final String TAG = "StreamedTestApp";
+
+ /**
+ * Tell this activity to call the {@link #EXTRA_ACTIVITY_LAUNCHED_RECEIVER} with
+ * {@link #RESULT_OK} when it is launched.
+ */
+ static final String ACTION_CALL_RESULT_RECEIVER =
+ "android.virtualdevice.streamedtestapp.CALL_RESULT_RECEIVER";
+ /**
+ * Extra in the result data that contains the integer display ID when the receiver for
+ * {@link #ACTION_CALL_RESULT_RECEIVER} is called.
+ */
+ static final String EXTRA_DISPLAY = "display";
+
+ /**
+ * Tell this activity to test clipboard when it is launched. This will attempt to read the
+ * existing string in clipboard, put that in the activity result (as
+ * {@link #EXTRA_CLIPBOARD_STRING}), and add the string in {@link #EXTRA_CLIPBOARD_STRING} in
+ * the intent extra to the clipboard.
+ */
+ static final String ACTION_TEST_CLIPBOARD =
+ "android.virtualdevice.streamedtestapp.TEST_CLIPBOARD";
+ static final String EXTRA_ACTIVITY_LAUNCHED_RECEIVER = "activityLaunchedReceiver";
+ static final String EXTRA_CLIPBOARD_STRING = "clipboardString";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ setTitle(getClass().getSimpleName());
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ String action = getIntent().getAction();
+ if (action != null) {
+ switch (action) {
+ case ACTION_CALL_RESULT_RECEIVER:
+ Log.d(TAG, "Handling intent receiver");
+ ResultReceiver resultReceiver =
+ getIntent().getParcelableExtra(EXTRA_ACTIVITY_LAUNCHED_RECEIVER);
+ Bundle result = new Bundle();
+ result.putInt(EXTRA_DISPLAY, getDisplay().getDisplayId());
+ resultReceiver.send(Activity.RESULT_OK, result);
+ finish();
+ break;
+ case ACTION_TEST_CLIPBOARD:
+ Log.d(TAG, "Testing clipboard");
+ testClipboard();
+ break;
+ default:
+ Log.w(TAG, "Unknown action: " + action);
+ }
+ }
+ }
+
+ private void testClipboard() {
+ Intent resultData = new Intent();
+ ClipboardManager clipboardManager = getSystemService(ClipboardManager.class);
+ resultData.putExtra(EXTRA_CLIPBOARD_STRING, clipboardManager.getPrimaryClip());
+
+ String clipboardContent = getIntent().getStringExtra(EXTRA_CLIPBOARD_STRING);
+ if (clipboardContent != null) {
+ clipboardManager.setPrimaryClip(
+ new ClipData(
+ "CTS clip data",
+ new String[] { "application/text" },
+ new ClipData.Item(clipboardContent)));
+ Log.d(TAG, "Wrote \"" + clipboardContent + "\" to clipboard");
+ } else {
+ Log.w(TAG, "Clipboard content is null");
+ }
+
+ setResult(Activity.RESULT_OK, resultData);
+ finish();
+ }
+}
+
diff --git a/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/NoEmbedActivity.java b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/NoEmbedActivity.java
new file mode 100644
index 0000000..3c77a46
--- /dev/null
+++ b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/NoEmbedActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.streamedtestapp;
+
+/**
+ * Same as MainActivity, but without allowEmbedded="true" in the manifest.
+ */
+public class NoEmbedActivity extends MainActivity {
+}
diff --git a/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/StreamedAppService.java b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/StreamedAppService.java
new file mode 100644
index 0000000..b270a5b
--- /dev/null
+++ b/tests/tests/virtualdevice/app/src/android/virtualdevice/streamedtestapp/StreamedAppService.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.streamedtestapp;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.util.Log;
+import android.virtualdevice.cts.IStreamedTestApp;
+
+/**
+ * Service for communicating between the CTS test and this streamed test app.
+ */
+public class StreamedAppService extends Service {
+
+ private static final String TAG = "StreamedAppService";
+
+ /**
+ * Tell this service to start {@link MainActivity}.
+ */
+ private static final String ACTION_START_MAIN_ACTIVITY =
+ "android.virtualdevice.streamedtestapp.START_MAIN_ACTIVITY";
+
+ /**
+ * Tell this service to do nothing.
+ */
+ private static final String ACTION_NO_OP = "android.virtualdevice.streamedtestapp.NO_OP";
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ switch (intent.getAction()) {
+ case ACTION_START_MAIN_ACTIVITY:
+ Intent activityIntent = new Intent(this, MainActivity.class)
+ .setAction(MainActivity.ACTION_CALL_RESULT_RECEIVER)
+ .putExtras(intent)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(activityIntent);
+ break;
+ case ACTION_NO_OP:
+ break;
+ default:
+ Log.w(TAG, "Unknown action: " + intent.getAction());
+ }
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new IStreamedTestApp.Stub() {
+
+ @Override
+ public PendingIntent createActivityPendingIntent(ResultReceiver resultReceiver) {
+ Intent intent = new Intent(MainActivity.ACTION_CALL_RESULT_RECEIVER)
+ .setClass(StreamedAppService.this, MainActivity.class);
+ intent.putExtra(MainActivity.EXTRA_ACTIVITY_LAUNCHED_RECEIVER, resultReceiver);
+ return PendingIntent.getActivity(
+ StreamedAppService.this, 1, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ @Override
+ public PendingIntent createServicePendingIntent(
+ boolean trampoline, ResultReceiver resultReceiver) {
+ Intent intent = new Intent(trampoline ? ACTION_START_MAIN_ACTIVITY : ACTION_NO_OP)
+ .setClass(StreamedAppService.this, StreamedAppService.class);
+ intent.putExtra(MainActivity.EXTRA_ACTIVITY_LAUNCHED_RECEIVER, resultReceiver);
+ return PendingIntent.getService(
+ StreamedAppService.this, 1, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+ };
+ }
+}
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityBlockingTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityBlockingTest.java
new file mode 100644
index 0000000..95797ef
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityBlockingTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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.virtualdevice.cts;
+
+import static android.Manifest.permission.ACTIVITY_EMBEDDING;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+import static android.Manifest.permission.REAL_GET_TASKS;
+import static android.Manifest.permission.WAKE_LOCK;
+import static android.virtualdevice.cts.util.VirtualDeviceTestUtils.createActivityOptions;
+import static android.virtualdevice.cts.util.VirtualDeviceTestUtils.createResultReceiver;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.virtualdevice.cts.util.EmptyActivity;
+import android.virtualdevice.cts.util.FakeAssociationRule;
+import android.virtualdevice.cts.util.TestAppHelper;
+import android.virtualdevice.cts.util.VirtualDeviceTestUtils.OnReceiveResultListener;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+
+/**
+ * Tests for blocking of activities that should not be shown on the virtual device.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityBlockingTest {
+
+ private static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS =
+ new VirtualDeviceParams.Builder().build();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ACTIVITY_EMBEDDING,
+ ADD_ALWAYS_UNLOCKED_DISPLAY,
+ ADD_TRUSTED_DISPLAY,
+ CREATE_VIRTUAL_DEVICE,
+ REAL_GET_TASKS,
+ WAKE_LOCK);
+
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+
+ private VirtualDeviceManager mVirtualDeviceManager;
+ @Nullable private VirtualDevice mVirtualDevice;
+ @Mock
+ private VirtualDisplay.Callback mVirtualDisplayCallback;
+ @Mock
+ private OnReceiveResultListener mOnReceiveResultListener;
+ private ResultReceiver mResultReceiver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ Context context = getApplicationContext();
+ assumeTrue(
+ context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP));
+
+ mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
+ mResultReceiver = createResultReceiver(mOnReceiveResultListener);
+ }
+
+ @After
+ public void tearDown() {
+ if (mVirtualDevice != null) {
+ mVirtualDevice.close();
+ }
+ }
+
+ @Test
+ public void nonTrustedDisplay_startNonEmbeddableActivity_shouldThrowSecurityException() {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ Intent intent = TestAppHelper.createNoEmbedIntent()
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ assertThrows(SecurityException.class, () ->
+ InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .startActivity(intent, createActivityOptions(virtualDisplay)));
+ }
+
+ @Test
+ public void cannotDisplayOnRemoteActivity_shouldBeBlockedFromLaunching() {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ EmptyActivity emptyActivity = (EmptyActivity) InstrumentationRegistry.getInstrumentation()
+ .startActivitySync(
+ new Intent(getApplicationContext(), EmptyActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ createActivityOptions(virtualDisplay));
+
+ emptyActivity.startActivity(
+ TestAppHelper.createCannotDisplayOnRemoteIntent(mResultReceiver));
+
+ verify(mOnReceiveResultListener, after(3000).never()).onReceiveResult(
+ eq(Activity.RESULT_OK),
+ argThat(result ->
+ result.getInt(TestAppHelper.EXTRA_DISPLAY)
+ == virtualDisplay.getDisplay().getDisplayId()));
+ }
+
+ @Test
+ public void trustedDisplay_startNonEmbeddableActivity_shouldSucceed() {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ Intent intent = TestAppHelper.createActivityLaunchedReceiverIntent(mResultReceiver)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .startActivity(intent, createActivityOptions(virtualDisplay));
+
+ verify(mOnReceiveResultListener, timeout(3000)).onReceiveResult(
+ eq(Activity.RESULT_OK),
+ argThat(result ->
+ result.getInt(TestAppHelper.EXTRA_DISPLAY)
+ == virtualDisplay.getDisplay().getDisplayId()));
+ }
+
+ @Test
+ public void setAllowedActivities_shouldBlockNonAllowedActivities() {
+ Context context = getApplicationContext();
+ ComponentName emptyActivityComponentName = new ComponentName(context, EmptyActivity.class);
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder()
+ .setAllowedActivities(Set.of(emptyActivityComponentName))
+ .build());
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ EmptyActivity emptyActivity = (EmptyActivity) InstrumentationRegistry.getInstrumentation()
+ .startActivitySync(
+ new Intent().setComponent(emptyActivityComponentName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ createActivityOptions(virtualDisplay));
+
+ emptyActivity.startActivity(
+ TestAppHelper.createActivityLaunchedReceiverIntent(mResultReceiver));
+
+ verify(mOnReceiveResultListener, after(3000).never()).onReceiveResult(
+ eq(Activity.RESULT_OK),
+ argThat(result ->
+ result.getInt(TestAppHelper.EXTRA_DISPLAY)
+ == virtualDisplay.getDisplay().getDisplayId()));
+ }
+
+ @Test
+ public void setBlockedActivities_shouldBlockActivityFromLaunching() {
+ Context context = getApplicationContext();
+ ComponentName emptyActivityComponentName = new ComponentName(context, EmptyActivity.class);
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder()
+ .setBlockedActivities(Set.of())
+ .build());
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ EmptyActivity emptyActivity = (EmptyActivity) InstrumentationRegistry.getInstrumentation()
+ .startActivitySync(
+ new Intent().setComponent(emptyActivityComponentName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ createActivityOptions(virtualDisplay));
+
+ EmptyActivity.Callback callback = mock(EmptyActivity.Callback.class);
+ emptyActivity.setCallback(callback);
+ final int requestCode = 1;
+ emptyActivity.startActivityForResult(TestAppHelper.createNoActionIntent(), requestCode);
+
+ verify(mOnReceiveResultListener, after(3000).never()).onReceiveResult(
+ eq(Activity.RESULT_OK),
+ argThat(result ->
+ result.getInt(TestAppHelper.EXTRA_DISPLAY)
+ == virtualDisplay.getDisplay().getDisplayId()));
+ }
+
+ @Test
+ public void setAllowedAndBlockedActivities_shouldThrowException() {
+ VirtualDeviceParams.Builder paramsBuilder = new VirtualDeviceParams.Builder();
+ assertThrows(IllegalArgumentException.class, () -> {
+ paramsBuilder.setAllowedActivities(Set.of(TestAppHelper.MAIN_ACTIVITY_COMPONENT));
+ paramsBuilder.setBlockedActivities(Set.of());
+ });
+ }
+
+ @Test
+ public void setBlockedAndAllowedActivities_shouldThrowException() {
+ VirtualDeviceParams.Builder paramsBuilder = new VirtualDeviceParams.Builder();
+ assertThrows(IllegalArgumentException.class, () -> {
+ paramsBuilder.setBlockedActivities(Set.of(TestAppHelper.MAIN_ACTIVITY_COMPONENT));
+ paramsBuilder.setAllowedActivities(Set.of());
+ });
+ }
+}
+
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityManagementTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityManagementTest.java
new file mode 100644
index 0000000..4bfb3ee
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityManagementTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.virtualdevice.cts;
+
+import static android.Manifest.permission.ACTIVITY_EMBEDDING;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+import static android.Manifest.permission.WAKE_LOCK;
+import static android.virtualdevice.cts.util.VirtualDeviceTestUtils.createActivityOptions;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager.ActivityListener;
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.virtualdevice.cts.util.EmptyActivity;
+import android.virtualdevice.cts.util.FakeAssociationRule;
+import android.virtualdevice.cts.util.TestAppHelper;
+import android.virtualdevice.cts.util.TestAppHelper.ServiceConnectionFuture;
+import android.virtualdevice.cts.util.VirtualDeviceTestUtils;
+import android.virtualdevice.cts.util.VirtualDeviceTestUtils.OnReceiveResultListener;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for activity management, like launching and listening to activity change events, in the
+ * virtual device.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagementTest {
+
+ private static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS =
+ new VirtualDeviceParams.Builder().build();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ACTIVITY_EMBEDDING,
+ ADD_ALWAYS_UNLOCKED_DISPLAY,
+ ADD_TRUSTED_DISPLAY,
+ CREATE_VIRTUAL_DEVICE,
+ WAKE_LOCK);
+
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+
+ private VirtualDeviceManager mVirtualDeviceManager;
+ @Nullable private VirtualDevice mVirtualDevice;
+ @Mock
+ private VirtualDisplay.Callback mVirtualDisplayCallback;
+ @Mock
+ private VirtualDeviceManager.LaunchCallback mLaunchCallback;
+ @Nullable private ServiceConnectionFuture<IStreamedTestApp> mServiceConnection;
+ @Mock
+ private OnReceiveResultListener mOnReceiveResultListener;
+ private ResultReceiver mResultReceiver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ Context context = getApplicationContext();
+ assumeTrue(
+ context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP));
+
+ mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
+ mResultReceiver = VirtualDeviceTestUtils.createResultReceiver(mOnReceiveResultListener);
+ }
+
+ @After
+ public void tearDown() {
+ try {
+ if (mServiceConnection != null) {
+ getApplicationContext().unbindService(mServiceConnection);
+ }
+ } catch (IllegalArgumentException e) {
+ // Ignore if the service failed to bind
+ }
+ if (mVirtualDevice != null) {
+ mVirtualDevice.close();
+ }
+ }
+
+ @Test
+ public void activityListener_shouldCallOnTopActivityChange() {
+ Context context = getApplicationContext();
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ ActivityListener activityListener = mock(ActivityListener.class);
+ mVirtualDevice.addActivityListener(activityListener);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+ EmptyActivity emptyActivity = (EmptyActivity) InstrumentationRegistry.getInstrumentation()
+ .startActivitySync(
+ new Intent(context, EmptyActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ createActivityOptions(virtualDisplay));
+
+ verify(activityListener).onTopActivityChanged(
+ eq(virtualDisplay.getDisplay().getDisplayId()),
+ eq(new ComponentName(context, EmptyActivity.class)));
+
+ emptyActivity.finish();
+
+ verify(activityListener, timeout(3000))
+ .onDisplayEmpty(eq(virtualDisplay.getDisplay().getDisplayId()));
+ }
+
+ @Test
+ public void launchPendingIntent_activityIntent_shouldLaunchActivity() throws Exception {
+ PendingIntent pendingIntent = getTestAppService()
+ .createActivityPendingIntent(mResultReceiver);
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ mVirtualDevice.launchPendingIntent(virtualDisplay.getDisplay().getDisplayId(),
+ pendingIntent, Runnable::run, mLaunchCallback);
+
+ verify(mOnReceiveResultListener, timeout(5000)).onReceiveResult(
+ eq(Activity.RESULT_OK), nullable(Bundle.class));
+ }
+
+ @Test
+ public void launchPendingIntent_serviceIntent_shouldLaunchTrampolineActivity()
+ throws Exception {
+ PendingIntent pendingIntent = getTestAppService()
+ .createServicePendingIntent(/* trampoline= */ true, mResultReceiver);
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ mVirtualDevice.launchPendingIntent(
+ virtualDisplay.getDisplay().getDisplayId(),
+ pendingIntent, Runnable::run,
+ mLaunchCallback);
+
+ verify(mOnReceiveResultListener, timeout(5000)).onReceiveResult(
+ eq(Activity.RESULT_OK), nullable(Bundle.class));
+ }
+
+ @Test
+ public void launchPendingIntent_serviceIntentNoTrampoline_shouldBeNoOp()
+ throws Exception {
+ PendingIntent pendingIntent = getTestAppService()
+ .createServicePendingIntent(/* trampoline=*/ false, mResultReceiver);
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+
+ mVirtualDevice.launchPendingIntent(
+ virtualDisplay.getDisplay().getDisplayId(),
+ pendingIntent,
+ Runnable::run,
+ mLaunchCallback);
+
+ verify(mOnReceiveResultListener, after(5000).never()).onReceiveResult(
+ eq(Activity.RESULT_OK), nullable(Bundle.class));
+ }
+
+ private IStreamedTestApp getTestAppService() throws Exception {
+ mServiceConnection = TestAppHelper.createTestAppService();
+ return mServiceConnection.getFuture().get(10, TimeUnit.SECONDS);
+ }
+}
+
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/CreateVirtualDisplayTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/CreateVirtualDisplayTest.java
new file mode 100644
index 0000000..dc338e1
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/CreateVirtualDisplayTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.virtualdevice.cts;
+
+import static android.Manifest.permission.ACTIVITY_EMBEDDING;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+import static android.Manifest.permission.WAKE_LOCK;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.Nullable;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Display;
+import android.virtualdevice.cts.util.FakeAssociationRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class CreateVirtualDisplayTest {
+
+ private static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS =
+ new VirtualDeviceParams.Builder().build();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ACTIVITY_EMBEDDING,
+ ADD_ALWAYS_UNLOCKED_DISPLAY,
+ ADD_TRUSTED_DISPLAY,
+ CREATE_VIRTUAL_DEVICE,
+ WAKE_LOCK);
+
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+
+ private VirtualDeviceManager mVirtualDeviceManager;
+ @Nullable private VirtualDevice mVirtualDevice;
+ @Mock
+ private VirtualDisplay.Callback mVirtualDisplayCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ Context context = getApplicationContext();
+ assumeTrue(
+ context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP));
+
+ mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
+ }
+
+ @After
+ public void tearDown() {
+ if (mVirtualDevice != null) {
+ mVirtualDevice.close();
+ }
+ }
+
+ @Test
+ public void createVirtualDisplay_shouldNotBeNull() {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+ assertThat(virtualDisplay).isNotNull();
+ int displayFlags = virtualDisplay.getDisplay().getFlags();
+ assertWithMessage(
+ String.format(
+ "Virtual display flags (0x%x) should contain FLAG_OWN_DISPLAY_GROUP",
+ displayFlags))
+ .that(displayFlags & Display.FLAG_OWN_DISPLAY_GROUP)
+ .isNotEqualTo(0);
+ }
+
+ @Ignore("Need allow_always_unlocked_virtual_displays flag to be on by default")
+ @Test
+ public void createVirtualDisplay_alwaysUnlocked_shouldSpecifyFlagInVirtualDisplays() {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder()
+ .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
+ .build());
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+ assertThat(virtualDisplay).isNotNull();
+ int displayFlags = virtualDisplay.getDisplay().getFlags();
+ assertWithMessage(
+ String.format(
+ "Virtual display flags (0x%x) should contain FLAG_ALWAYS_UNLOCKED",
+ displayFlags))
+ .that(displayFlags & Display.FLAG_ALWAYS_UNLOCKED)
+ .isNotEqualTo(0);
+ }
+}
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/StreamedAppBehaviorTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/StreamedAppBehaviorTest.java
new file mode 100644
index 0000000..f5141d0
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/StreamedAppBehaviorTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.virtualdevice.cts;
+
+import static android.Manifest.permission.ACTIVITY_EMBEDDING;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+import static android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND;
+import static android.Manifest.permission.WAKE_LOCK;
+import static android.virtualdevice.cts.util.VirtualDeviceTestUtils.createActivityOptions;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager.ActivityListener;
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Handler;
+import android.os.Looper;
+import android.virtualdevice.cts.util.EmptyActivity;
+import android.virtualdevice.cts.util.FakeAssociationRule;
+import android.virtualdevice.cts.util.TestAppHelper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class StreamedAppBehaviorTest {
+
+ private static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS =
+ new VirtualDeviceParams.Builder().build();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ACTIVITY_EMBEDDING,
+ ADD_TRUSTED_DISPLAY,
+ CREATE_VIRTUAL_DEVICE,
+ READ_CLIPBOARD_IN_BACKGROUND,
+ WAKE_LOCK);
+
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+
+ private VirtualDeviceManager mVirtualDeviceManager;
+ @Nullable private VirtualDevice mVirtualDevice;
+ @Mock
+ private VirtualDisplay.Callback mVirtualDisplayCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ Context context = getApplicationContext();
+ assumeTrue(
+ context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP));
+
+ mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
+ }
+
+ @After
+ public void tearDown() {
+ if (mVirtualDevice != null) {
+ mVirtualDevice.close();
+ }
+ }
+
+ @Test
+ public void appsInVirtualDevice_shouldNotHaveAccessToClipboard() {
+ Context context = getApplicationContext();
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ ActivityListener activityListener = mock(ActivityListener.class);
+ mVirtualDevice.addActivityListener(activityListener);
+ VirtualDisplay virtualDisplay = mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ new Handler(Looper.getMainLooper()),
+ mVirtualDisplayCallback);
+ ClipboardManager clipboardManager = context.getSystemService(ClipboardManager.class);
+ clipboardManager.setPrimaryClip(
+ new ClipData(
+ "CTS test clip",
+ new String[] { "application/text" },
+ new ClipData.Item("clipboard content from test")));
+
+ EmptyActivity activity = (EmptyActivity) InstrumentationRegistry.getInstrumentation()
+ .startActivitySync(
+ new Intent(context, EmptyActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ createActivityOptions(virtualDisplay));
+
+ EmptyActivity.Callback callback = mock(EmptyActivity.Callback.class);
+ activity.setCallback(callback);
+
+ int requestCode = 1;
+ activity.startActivityForResult(
+ TestAppHelper.createClipboardTestIntent("clipboard content from app"),
+ requestCode,
+ createActivityOptions(virtualDisplay));
+
+ ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(callback, timeout(10000)).onActivityResult(
+ eq(requestCode), eq(Activity.RESULT_OK), intentArgumentCaptor.capture());
+ Intent resultData = intentArgumentCaptor.getValue();
+ // This is important to get us off of the virtual display so we can read the clipboard
+ activity.finish();
+
+ assertThat(resultData).isNotNull();
+ ClipData appReadClipData = resultData.getParcelableExtra("readClip");
+ assertThat(appReadClipData).isNull();
+ verify(activityListener, timeout(3000))
+ .onDisplayEmpty(eq(virtualDisplay.getDisplay().getDisplayId()));
+ assertThat(clipboardManager.getPrimaryClip().getItemAt(0).getText().toString())
+ .isEqualTo("clipboard content from test");
+ }
+}
+
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualDeviceManagerBasicTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualDeviceManagerBasicTest.java
new file mode 100644
index 0000000..b2e889e
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualDeviceManagerBasicTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.virtualdevice.cts;
+
+import static android.Manifest.permission.ACTIVITY_EMBEDDING;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.Nullable;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.virtualdevice.cts.util.FakeAssociationRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualDeviceManagerBasicTest {
+
+ private static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS =
+ new VirtualDeviceParams.Builder().build();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ACTIVITY_EMBEDDING,
+ ADD_ALWAYS_UNLOCKED_DISPLAY,
+ CREATE_VIRTUAL_DEVICE);
+
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+
+ private VirtualDeviceManager mVirtualDeviceManager;
+ @Nullable private VirtualDevice mVirtualDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ Context context = getApplicationContext();
+ assumeTrue(
+ context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP));
+
+ mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
+ }
+
+ @After
+ public void tearDown() {
+ if (mVirtualDevice != null) {
+ mVirtualDevice.close();
+ }
+ }
+
+ @Test
+ public void createVirtualDevice_shouldNotThrowException() {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ assertThat(mVirtualDevice).isNotNull();
+ }
+
+ @Test
+ public void createVirtualDevice_noPermission_shouldThrowSecurityException() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+
+ assertThrows(
+ SecurityException.class,
+ () -> mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS));
+ }
+
+ @Test
+ public void createVirtualDevice_invalidAssociationId_shouldThrowIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mVirtualDeviceManager.createVirtualDevice(
+ /* associationId= */ -1,
+ DEFAULT_VIRTUAL_DEVICE_PARAMS));
+ }
+}
+
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualInputTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualInputTest.java
new file mode 100644
index 0000000..983df12
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualInputTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.virtualdevice.cts;
+
+import static android.Manifest.permission.ACTIVITY_EMBEDDING;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+import static android.Manifest.permission.WAKE_LOCK;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.Nullable;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.virtualdevice.cts.util.FakeAssociationRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualInputTest {
+
+ private static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS =
+ new VirtualDeviceParams.Builder().build();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ACTIVITY_EMBEDDING,
+ ADD_ALWAYS_UNLOCKED_DISPLAY,
+ ADD_TRUSTED_DISPLAY,
+ CREATE_VIRTUAL_DEVICE,
+ WAKE_LOCK);
+
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+
+ private VirtualDeviceManager mVirtualDeviceManager;
+ @Nullable private VirtualDevice mVirtualDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ Context context = getApplicationContext();
+ assumeTrue(
+ context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP));
+
+ mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
+ }
+
+ @After
+ public void tearDown() {
+ if (mVirtualDevice != null) {
+ mVirtualDevice.close();
+ }
+ }
+
+ @Test
+ public void createVirtualKeyboard_noDisplay_shouldThrowSecurityException() {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ DisplayManager displayManager =
+ getApplicationContext().getSystemService(DisplayManager.class);
+ // Virtual displays created directly using DisplayManager should not be allowed to create
+ // associated virtual keyboards
+ VirtualDisplay testVirtualDisplay = displayManager.createVirtualDisplay(
+ /* displayName= */ "testVirtualDisplay",
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 100,
+ /* surface= */ null,
+ /* flags= */ 0);
+
+ assertThrows(
+ SecurityException.class,
+ () -> mVirtualDevice.createVirtualKeyboard(
+ testVirtualDisplay,
+ "testVirtualKeyboard",
+ /* vendorId= */ 0,
+ /* productId= */ 0));
+ }
+}
+
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/EmptyActivity.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/EmptyActivity.java
new file mode 100644
index 0000000..53ed9a9
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/EmptyActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.cts.util;
+
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Intent;
+
+/**
+ * An empty activity to allow this CTS test to get foreground status, for things like accessing
+ * clipboard data.
+ */
+public class EmptyActivity extends Activity {
+
+ public interface Callback {
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+ @Nullable private Callback mCallback;
+
+ public void setCallback(@Nullable Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (mCallback != null) {
+ mCallback.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/FakeAssociationRule.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/FakeAssociationRule.java
new file mode 100644
index 0000000..555f7c1
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/FakeAssociationRule.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.cts.util;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceManager;
+import android.os.Process;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.List;
+
+/**
+ * A test rule that creates a {@link CompanionDeviceManager} association with the instrumented
+ * package for the duration of the test.
+ */
+public class FakeAssociationRule extends ExternalResource {
+
+ private static final String FAKE_ASSOCIATION_ADDRESS = "00:00:00:00:00:10";
+
+ private AssociationInfo mAssociationInfo;
+ private CompanionDeviceManager mCompanionDeviceManager;
+
+ @Override
+ protected void before() throws Throwable {
+ super.before();
+ mCompanionDeviceManager =
+ getApplicationContext().getSystemService(CompanionDeviceManager.class);
+ clearExistingAssociations();
+ SystemUtil.runShellCommand(String.format("cmd companiondevice associate %d %s %s",
+ Process.myUserHandle().getIdentifier(),
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName(),
+ FAKE_ASSOCIATION_ADDRESS));
+ List<AssociationInfo> associations = mCompanionDeviceManager.getMyAssociations();
+ assertThat(associations).hasSize(1);
+ mAssociationInfo = associations.get(0);
+ }
+
+ @Override
+ protected void after() {
+ super.after();
+ if (mAssociationInfo != null) {
+ mCompanionDeviceManager.disassociate(mAssociationInfo.getId());
+ }
+ }
+
+ private void clearExistingAssociations() {
+ List<AssociationInfo> associations = mCompanionDeviceManager.getMyAssociations();
+ for (AssociationInfo association : associations) {
+ mCompanionDeviceManager.disassociate(association.getId());
+ }
+ assertThat(mCompanionDeviceManager.getMyAssociations()).isEmpty();
+ }
+
+ public AssociationInfo getAssociationInfo() {
+ return mAssociationInfo;
+ }
+}
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/TestAppHelper.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/TestAppHelper.java
new file mode 100644
index 0000000..e8a8f9f
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/TestAppHelper.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.cts.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.ResultReceiver;
+import android.virtualdevice.cts.IStreamedTestApp;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * Helper for interacting with {@code android.virtualdevice.streamedtestapp}.
+ */
+public class TestAppHelper {
+ static final String PACKAGE_NAME = "android.virtualdevice.streamedtestapp";
+ static final String MAIN_ACTIVITY = "android.virtualdevice.streamedtestapp.MainActivity";
+ static final String NO_EMBED_ACTIVITY = "android.virtualdevice.streamedtestapp.NoEmbedActivity";
+ static final String CANNOT_DISPLAY_ON_REMOTE_ACTIVITY =
+ "android.virtualdevice.streamedtestapp.CannotDisplayOnRemoteActivity";
+ static final String STREAMED_APP_SERVICE =
+ "android.virtualdevice.streamedtestapp.StreamedAppService";
+
+ /** @see android.virtualdevice.streamedtestapp.MainActivity */
+ static final String ACTION_TEST_CLIPBOARD =
+ "android.virtualdevice.streamedtestapp.TEST_CLIPBOARD";
+ /** @see android.virtualdevice.streamedtestapp.MainActivity */
+ static final String EXTRA_CLIPBOARD_STRING = "clipboardString";
+
+ static final String ACTION_CALL_RESULT_RECEIVER =
+ "android.virtualdevice.streamedtestapp.CALL_RESULT_RECEIVER";
+ static final String EXTRA_ACTIVITY_LAUNCHED_RECEIVER = "activityLaunchedReceiver";
+ public static final String EXTRA_DISPLAY = "display";
+
+ public static final ComponentName MAIN_ACTIVITY_COMPONENT = new ComponentName(
+ TestAppHelper.PACKAGE_NAME, TestAppHelper.MAIN_ACTIVITY);
+
+ public static Intent createClipboardTestIntent(String clipboardString) {
+ return new Intent(ACTION_TEST_CLIPBOARD)
+ .setComponent(MAIN_ACTIVITY_COMPONENT)
+ .putExtra(EXTRA_CLIPBOARD_STRING, clipboardString);
+ }
+
+ public static Intent createNoActionIntent() {
+ return new Intent().setComponent(MAIN_ACTIVITY_COMPONENT);
+ }
+
+ public static Intent createNoEmbedIntent() {
+ return new Intent().setClassName(PACKAGE_NAME, NO_EMBED_ACTIVITY);
+ }
+
+ public static Intent createCannotDisplayOnRemoteIntent(ResultReceiver resultReceiver) {
+ return new Intent(ACTION_CALL_RESULT_RECEIVER)
+ .setClassName(PACKAGE_NAME, CANNOT_DISPLAY_ON_REMOTE_ACTIVITY)
+ .putExtra(EXTRA_ACTIVITY_LAUNCHED_RECEIVER, resultReceiver);
+ }
+
+ public static Intent createActivityLaunchedReceiverIntent(ResultReceiver resultReceiver) {
+ return new Intent(ACTION_CALL_RESULT_RECEIVER)
+ .setComponent(MAIN_ACTIVITY_COMPONENT)
+ .putExtra(EXTRA_ACTIVITY_LAUNCHED_RECEIVER, resultReceiver);
+ }
+
+ public static ServiceConnectionFuture<IStreamedTestApp> createTestAppService() {
+ Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ ServiceConnectionFuture<IStreamedTestApp> connection =
+ new ServiceConnectionFuture<IStreamedTestApp>(IStreamedTestApp.Stub::asInterface);
+ boolean bindResult = targetContext.bindService(
+ new Intent().setClassName(PACKAGE_NAME, STREAMED_APP_SERVICE),
+ connection,
+ Context.BIND_AUTO_CREATE);
+ assertThat(bindResult).isTrue();
+ return connection;
+ }
+
+ public static class ServiceConnectionFuture<T extends IInterface> implements ServiceConnection {
+
+ private final CompletableFuture<T> mFuture = new CompletableFuture<>();
+ private final Function<IBinder, T> mAsInterfaceFunc;
+
+ ServiceConnectionFuture(Function<IBinder, T> asInterfaceFunc) {
+ mAsInterfaceFunc = asInterfaceFunc;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mFuture.complete(mAsInterfaceFunc.apply(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // If the future is already completed, then it will stay completed with the old value.
+ mFuture.completeExceptionally(new Exception("Service disconnected"));
+ }
+
+ public CompletableFuture<T> getFuture() {
+ return mFuture;
+ }
+ }
+}
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/VirtualDeviceTestUtils.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/VirtualDeviceTestUtils.java
new file mode 100644
index 0000000..926250d
--- /dev/null
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/util/VirtualDeviceTestUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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.virtualdevice.cts.util;
+
+import android.app.ActivityOptions;
+import android.hardware.display.VirtualDisplay;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+
+/**
+ * Test utilities for Virtual Device tests.
+ */
+public final class VirtualDeviceTestUtils {
+
+ public static ResultReceiver createResultReceiver(OnReceiveResultListener listener) {
+ ResultReceiver receiver = new ResultReceiver(new Handler(Looper.getMainLooper())) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ listener.onReceiveResult(resultCode, resultData);
+ }
+ };
+ // Erase the subclass to make the given result receiver safe to include inside Bundles
+ // (See b/177985835).
+ Parcel parcel = Parcel.obtain();
+ receiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ receiver = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return receiver;
+ }
+
+ /**
+ * Interface mimicking {@link ResultReceiver}, allowing it to be mocked.
+ */
+ public interface OnReceiveResultListener {
+ void onReceiveResult(int resultCode, Bundle resultData);
+ }
+
+ public static Bundle createActivityOptions(VirtualDisplay virtualDisplay) {
+ return ActivityOptions.makeBasic()
+ .setLaunchDisplayId(virtualDisplay.getDisplay().getDisplayId())
+ .toBundle();
+ }
+
+ private VirtualDeviceTestUtils() {}
+}