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() {}
+}