Add test for INSTALL_PACKAGES when adding installer package

Verifies that INSTALL_PACKAGES is required to add an installer
to an existing package and that an app cannot exploit a previous
vulnerability allowing it to grant itself whitelist restricted
permissions.

Bug: 150857253

Test: atest android.appsecurity.cts.PackageSetInstallerTest
Test: atest CtsMediaHostTestCases
Test: cts-tradefed run commandAndExit cts --abi armeabi-v7a
    -m CtsMediaBitstreamsTestCases

Change-Id: Ib94bc437d6738821f58367216b711096459768c2
diff --git a/hostsidetests/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp
index 3cc61fe..7a77cc9 100644
--- a/hostsidetests/appsecurity/Android.bp
+++ b/hostsidetests/appsecurity/Android.bp
@@ -17,7 +17,7 @@
     defaults: ["cts_defaults"],
 
     // Only compile source java files in this apk.
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.java", "src/**/*.kt"],
 
     libs: [
         "cts-tradefed",
@@ -27,7 +27,11 @@
         "hamcrest-library",
     ],
 
-    static_libs: ["CompatChangeGatingTestBase"],
+    static_libs: [
+        "CompatChangeGatingTestBase",
+        "CtsPkgInstallerConstants",
+        "cts-host-utils",
+    ],
 
     java_resource_dirs: ["res"],
 
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
index 24cc5ae..283d3cb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
@@ -28,7 +28,7 @@
 /**
  * Base class.
  */
-abstract class BaseAppSecurityTest extends BaseHostJUnit4Test {
+public abstract class BaseAppSecurityTest extends BaseHostJUnit4Test {
 
     /** Whether multi-user is supported. */
     protected boolean mSupportsMultiUser;
@@ -92,7 +92,10 @@
             this(false);
         }
         public InstallMultiple(boolean instant) {
-            super(getDevice(), getBuild(), getAbi());
+            this(instant, true);
+        }
+        public InstallMultiple(boolean instant, boolean grantPermissions) {
+            super(getDevice(), getBuild(), getAbi(), grantPermissions);
             addArg(instant ? "--instant" : "");
             addArg("--force-queryable");
         }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
index cb170c9..00f4975 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
@@ -51,10 +51,17 @@
     private boolean mUseNaturalAbi;
 
     public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) {
+        this(device, buildInfo, abi, true);
+    }
+
+    public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi,
+            boolean grantPermissions) {
         mDevice = device;
         mBuild = buildInfo;
         mAbi = abi;
-        addArg("-g");
+        if (grantPermissions) {
+            addArg("-g");
+        }
     }
 
     T addArg(String arg) {
@@ -114,6 +121,11 @@
         return (T) this;
     }
 
+    T restrictPermissions() {
+        addArg("--restrict-permissions");
+        return (T) this;
+    }
+
     protected String deriveRemoteName(String originalName, int index) {
         return index + "_" + originalName;
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageSetInstallerTest.kt b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageSetInstallerTest.kt
new file mode 100644
index 0000000..f3c3420
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageSetInstallerTest.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 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.appsecurity.cts
+
+import android.appsecurity.cts.PackageSetInstallerConstants.CHANGE_ID
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_HARD_RESTRICTED
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_IMMUTABLY_SOFT_RESTRICTED
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_NOT_RESTRICTED
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_SUCCEED_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_THROW_EXCEPTION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.TARGET_APK
+import android.appsecurity.cts.PackageSetInstallerConstants.TARGET_PKG
+import android.appsecurity.cts.PackageSetInstallerConstants.WHITELIST_APK
+import android.appsecurity.cts.PackageSetInstallerConstants.WHITELIST_PKG
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters
+import android.cts.host.utils.DeviceJUnit4Parameterized
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * This test verifies protection for an exploit where any app could set the installer package
+ * name for another app if the installer was uninstalled or never set.
+ *
+ * It mimics both the set installer logic and checks for a permission bypass caused by this exploit,
+ * where an app could take installer for itself and whitelist itself to receive protected
+ * permissions.
+ */
+@RunWith(DeviceJUnit4Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(
+        DeviceJUnit4ClassRunnerWithParameters.RunnerFactory::class)
+class PackageSetInstallerTest : BaseAppSecurityTest() {
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{1}")
+        fun parameters() = arrayOf(
+                arrayOf(true, "throwException"),
+                arrayOf(false, "failSilently")
+        )
+    }
+
+    @JvmField
+    @Parameterized.Parameter(0)
+    var failSilently = false
+
+    @Parameterized.Parameter(1)
+    lateinit var testName: String
+
+    @Before
+    @After
+    fun uninstallTestPackages() {
+        device.uninstallPackage(TARGET_PKG)
+        device.uninstallPackage(WHITELIST_PKG)
+    }
+
+    @After
+    fun resetChanges() {
+        device.executeShellCommand("am compat reset $CHANGE_ID $TARGET_PKG")
+        device.executeShellCommand("am compat reset $CHANGE_ID $WHITELIST_PKG")
+    }
+
+    @Before
+    fun initializeChangeState() {
+        if (failSilently) {
+            device.executeShellCommand("am compat disable $CHANGE_ID $TARGET_PKG")
+            device.executeShellCommand("am compat disable $CHANGE_ID $WHITELIST_PKG")
+        } else {
+            resetChanges()
+        }
+    }
+
+    @Test
+    fun notRestricted() {
+        runTest(removeWhitelistShouldSucceed = false,
+                permission = PERMISSION_NOT_RESTRICTED,
+                finalState = GrantState.TRUE)
+    }
+
+    @Test
+    fun hardRestricted() {
+        runTest(removeWhitelistShouldSucceed = true,
+                permission = PERMISSION_HARD_RESTRICTED,
+                finalState = GrantState.FALSE)
+    }
+
+    @Test
+    fun immutablySoftRestrictedGranted() {
+        runTest(removeWhitelistShouldSucceed = null,
+                permission = PERMISSION_IMMUTABLY_SOFT_RESTRICTED,
+                finalState = GrantState.TRUE_EXEMPT)
+    }
+
+    @Test
+    fun immutablySoftRestrictedRevoked() {
+        runTest(removeWhitelistShouldSucceed = null,
+                permission = PERMISSION_IMMUTABLY_SOFT_RESTRICTED,
+                restrictPermissions = true,
+                finalState = GrantState.TRUE_RESTRICTED)
+    }
+
+    private fun runTest(
+        removeWhitelistShouldSucceed: Boolean?,
+        permission: String,
+        restrictPermissions: Boolean = false,
+        finalState: GrantState
+    ) {
+        // Verifies throwing a SecurityException or failing silently for backwards compatibility
+        val testArgs: Map<String, String?> = mapOf(
+                PERMISSION_KEY to permission
+        )
+
+        // First, install both packages and ensure no installer is set
+        InstallMultiple(false, false)
+                .addFile(TARGET_APK)
+                .allowTest()
+                .forUser(mPrimaryUserId)
+                .apply {
+                    if (restrictPermissions) {
+                        restrictPermissions()
+                    }
+                }
+                .run()
+
+        InstallMultiple(false, false)
+                .addFile(WHITELIST_APK)
+                .allowTest()
+                .forUser(mPrimaryUserId)
+                .run()
+
+        assertPermission(false, permission)
+        assertTargetInstaller(null)
+
+        // Install the installer whitelist app and take over the installer package. This methods
+        // adopts the INSTALL_PACKAGES permission and verifies that the new behavior of checking
+        // this permission is applied.
+        Utils.runDeviceTests(device, WHITELIST_PKG, ".PermissionWhitelistTest",
+                "setTargetInstallerPackage", mPrimaryUserId,
+                testArgs.plus(SHOULD_THROW_EXCEPTION_KEY to (!failSilently).toString()))
+        assertTargetInstaller(WHITELIST_PKG)
+
+        // Verify that without whitelist restriction, the target app can be granted the permission
+        grantPermission(permission)
+        assertPermission(true, permission)
+        revokePermission(permission)
+        assertPermission(false, permission)
+
+        val whitelistArgs = testArgs
+                .plus(SHOULD_SUCCEED_KEY to removeWhitelistShouldSucceed?.toString())
+                .filterValues { it != null }
+
+        // Now restrict the permission from the target app using the whitelist app
+        Utils.runDeviceTests(device, WHITELIST_PKG, ".PermissionWhitelistTest",
+                "removeWhitelistRestrictedPermission", mPrimaryUserId, whitelistArgs)
+
+        // Now remove the installer and verify the installer is wiped
+        device.uninstallPackage(WHITELIST_PKG)
+        assertTargetInstaller(null)
+
+        // Verify whitelist restriction retained by attempting and failing to grant permission
+        assertPermission(false, permission)
+        grantPermission(permission)
+        assertGrantState(finalState, permission)
+        revokePermission(permission)
+
+        // Attempt exploit to take over installer package and have target whitelist itself
+        Utils.runDeviceTests(device, TARGET_PKG, ".PermissionRequestTest",
+                "setSelfAsInstallerAndWhitelistPermission", mPrimaryUserId,
+                testArgs.plus(SHOULD_THROW_EXCEPTION_KEY to (!failSilently).toString()))
+
+        // Assert nothing changed about whitelist restriction
+        assertTargetInstaller(null)
+        grantPermission(permission)
+        assertGrantState(finalState, permission)
+    }
+
+    private fun assertTargetInstaller(installer: String?) {
+        assertThat(device.executeShellCommand("pm list packages -i | grep $TARGET_PKG").trim())
+                .isEqualTo("package:$TARGET_PKG  installer=$installer")
+    }
+
+    private fun assertPermission(granted: Boolean, permission: String) {
+        assertThat(device.executeShellCommand("dumpsys package $TARGET_PKG | grep $permission"))
+                .contains("$permission: granted=$granted")
+    }
+
+    private fun grantPermission(permission: String) {
+        device.executeShellCommand("pm grant $TARGET_PKG $permission")
+    }
+
+    private fun revokePermission(permission: String) {
+        device.executeShellCommand("pm revoke $TARGET_PKG $permission")
+    }
+
+    private fun assertGrantState(state: GrantState, permission: String) {
+        val output = device.executeShellCommand(
+                "dumpsys package $TARGET_PKG | grep \"$permission: granted\"").trim()
+
+        // Make sure only the expected output line is returned
+        assertWithMessage(output).that(output.lines().size).isEqualTo(1)
+
+        when (state) {
+            GrantState.TRUE -> {
+                assertThat(output).contains("granted=true")
+                assertThat(output).doesNotContain("RESTRICTION")
+                assertThat(output).doesNotContain("EXEMPT")
+            }
+            GrantState.TRUE_EXEMPT -> {
+                assertThat(output).contains("granted=true")
+                assertThat(output).contains("RESTRICTION_INSTALLER_EXEMPT")
+            }
+            GrantState.TRUE_RESTRICTED -> {
+                assertThat(output).contains("granted=true")
+                assertThat(output).contains("APPLY_RESTRICTION")
+                assertThat(output).doesNotContain("EXEMPT")
+            }
+            GrantState.FALSE -> {
+                assertThat(output).contains("granted=false")
+            }
+        }
+    }
+
+    enum class GrantState {
+        // Granted in full, unrestricted
+        TRUE,
+
+        // Granted in full by exemption
+        TRUE_EXEMPT,
+
+        // Granted in part
+        TRUE_RESTRICTED,
+
+        // Not granted at all
+        FALSE
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
new file mode 100644
index 0000000..990ef49
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+    name: "CtsPkgInstallerPermRequestApp",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+        "CtsPkgInstallerConstants",
+    ],
+    srcs: ["src/**/*.kt"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+    // sign this app with a different cert than CtsPkgInstallerPermWhitelistApp
+    certificate: ":cts-testkey2",
+}
+
+java_library {
+    name: "CtsPkgInstallerConstants",
+    srcs: ["utils/src/**/PackageSetInstallerConstants.kt"],
+    host_supported: true,
+}
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/AndroidManifest.xml
new file mode 100644
index 0000000..7d0ab56
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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="com.android.cts.packageinstallerpermissionrequestapp">
+
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.packageinstallerpermissionrequestapp"
+                     android:label="Test for unset package installer permission exploit."/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/src/com/android/cts/packageinstallerpermissionrequestapp/PermissionRequestTest.kt b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/src/com/android/cts/packageinstallerpermissionrequestapp/PermissionRequestTest.kt
new file mode 100644
index 0000000..1ca6a7f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/src/com/android/cts/packageinstallerpermissionrequestapp/PermissionRequestTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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 com.android.cts.packageinstallerpermissionrequestapp
+
+import android.Manifest
+import android.appsecurity.cts.PackageSetInstallerConstants
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_THROW_EXCEPTION_KEY
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+
+class PermissionRequestTest {
+
+    @get:Rule
+    val expectedException = ExpectedException.none()
+
+    private val arguments: Bundle = InstrumentationRegistry.getArguments()
+    private val permission = arguments.getString(PackageSetInstallerConstants.PERMISSION_KEY)!!
+    private val context: Context = InstrumentationRegistry.getContext()
+    private val packageName = context.packageName
+    private val packageManager = context.packageManager
+
+    @Before
+    @After
+    fun verifyPermissionsDeniedAndInstallerUnchanged() {
+        assertThat(context.checkSelfPermission(permission))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+        assertThat(context.checkSelfPermission(Manifest.permission.INSTALL_PACKAGES))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+        assertThat(packageManager.getInstallerPackageName(packageName))
+                .isEqualTo(null)
+    }
+
+    @Test
+    fun setSelfAsInstallerAndWhitelistPermission() {
+        val shouldThrowException = arguments.getString(SHOULD_THROW_EXCEPTION_KEY)!!.toBoolean()
+        if (shouldThrowException) {
+            expectedException.expect(SecurityException::class.java)
+        }
+
+        try {
+            // This set call should fail
+            packageManager.setInstallerPackageName(packageName, packageName)
+        } finally {
+            // But also call the whitelist method to attempt to take the permission regardless.
+            // If this fails, which it should, it should also be a SecurityException.
+            try {
+                packageManager.addWhitelistedRestrictedPermission(packageName,
+                        permission, PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)
+                Assert.fail("addWhitelistedRestrictedPermission did not throw SecurityException")
+            } catch (ignored: SecurityException) {
+                // Ignore this to defer to shouldThrowException from above call
+            }
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/utils/src/android/appsecurity/cts/PackageSetInstallerConstants.kt b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/utils/src/android/appsecurity/cts/PackageSetInstallerConstants.kt
new file mode 100644
index 0000000..723cb94
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/utils/src/android/appsecurity/cts/PackageSetInstallerConstants.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.appsecurity.cts
+
+// Placed in CtsPkgInstallerPermRequestApp sources as its manifest declares what permissions to test
+object PackageSetInstallerConstants {
+
+    // The app that will be attempting to take over as installer and grant itself permissions
+    const val TARGET_APK = "CtsPkgInstallerPermRequestApp.apk"
+    const val TARGET_PKG = "com.android.cts.packageinstallerpermissionrequestapp"
+
+    // The app acting as the original installer who can restrict permissions
+    const val WHITELIST_APK = "CtsPkgInstallerPermWhitelistApp.apk"
+    const val WHITELIST_PKG = "com.android.cts.packageinstallerpermissionwhitelistapp"
+
+    const val PERMISSION_HARD_RESTRICTED = "android.permission.SEND_SMS"
+    const val PERMISSION_NOT_RESTRICTED = "android.permission.ACCESS_COARSE_LOCATION"
+    const val PERMISSION_IMMUTABLY_SOFT_RESTRICTED = "android.permission.READ_EXTERNAL_STORAGE"
+    const val PERMISSION_KEY = "permission"
+
+    // Whether setInstallerPackageName should throw
+    const val SHOULD_THROW_EXCEPTION_KEY = "shouldThrowException"
+
+    // Whether or not some boolean return method call should succeed
+    const val SHOULD_SUCCEED_KEY = "shouldSucceed"
+
+    // PackageManagerService.THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE
+    const val CHANGE_ID = 150857253
+}
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
new file mode 100644
index 0000000..b731a05
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+    name: "CtsPkgInstallerPermWhitelistApp",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "CtsPkgInstallerConstants",
+        "testng",
+    ],
+    srcs: ["src/**/*.kt"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+    // sign this app with a different cert than CtsPkgInstallerPermRequestApp
+    certificate: ":cts-testkey1",
+}
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/AndroidManifest.xml
new file mode 100644
index 0000000..d54b9c5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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="com.android.cts.packageinstallerpermissionwhitelistapp">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.packageinstallerpermissionwhitelistapp"
+                     android:label="Test for unset package installer permission exploit."/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/src/com/android/cts/packageinstallerpermissionwhitelistapp/PermissionWhitelistTest.kt b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/src/com/android/cts/packageinstallerpermissionwhitelistapp/PermissionWhitelistTest.kt
new file mode 100644
index 0000000..464047e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/src/com/android/cts/packageinstallerpermissionwhitelistapp/PermissionWhitelistTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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 com.android.cts.packageinstallerpermissionwhitelistapp
+
+import android.Manifest
+import android.appsecurity.cts.PackageSetInstallerConstants
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_THROW_EXCEPTION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.TARGET_PKG
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.test.InstrumentationRegistry
+import com.android.compatibility.common.util.ShellIdentityUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.testng.Assert.assertThrows
+
+class PermissionWhitelistTest {
+
+    private val arguments: Bundle = InstrumentationRegistry.getArguments()
+    private val shouldThrowException = arguments.getString(SHOULD_THROW_EXCEPTION_KEY)
+            ?.toBoolean() ?: false
+    private val permission = arguments.getString(PERMISSION_KEY)!!
+    private val context: Context = InstrumentationRegistry.getContext()
+    private val packageName = context.packageName
+    private val packageManager = context.packageManager
+
+    @Before
+    fun verifyNoInstallPackagesPermissions() {
+        // This test adopts shell permissions to get INSTALL_PACKAGES. That ensures that the
+        // whitelist package having INSTALL_PACKAGES doesn't bypass any checks. In a realistic
+        // scenario, this whitelisting app would request install, not directly install the target
+        // package.
+        assertThat(context.checkSelfPermission(Manifest.permission.INSTALL_PACKAGES))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+    }
+
+    @Test
+    fun setTargetInstallerPackage() {
+        assertTargetInstaller(null)
+
+        val block = {
+            packageManager.setInstallerPackageName(TARGET_PKG, packageName)
+        }
+
+        if (shouldThrowException) {
+            assertThrows(SecurityException::class.java) { block() }
+        } else {
+            block()
+        }
+
+        assertTargetInstaller(null)
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(packageManager,
+                ShellIdentityUtils.ShellPermissionMethodHelperNoReturn {
+                    it.setInstallerPackageName(TARGET_PKG, packageName)
+                }, Manifest.permission.INSTALL_PACKAGES)
+        assertTargetInstaller(packageName)
+    }
+
+    @Test
+    fun removeWhitelistRestrictedPermission() {
+        val block = {
+            packageManager.removeWhitelistedRestrictedPermission(TARGET_PKG, permission,
+                    PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)
+        }
+
+        val shouldSucceed = arguments.getString(
+                PackageSetInstallerConstants.SHOULD_SUCCEED_KEY)
+        if (shouldSucceed == null) {
+            assertThrows(SecurityException::class.java) { block() }
+        } else {
+            assertThat(block()).isEqualTo(shouldSucceed.toBoolean())
+        }
+
+        assertThat(packageManager.checkPermission(permission, TARGET_PKG))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+    }
+
+    private fun assertTargetInstaller(installer: String?) {
+        assertThat(packageManager.getInstallerPackageName(TARGET_PKG))
+                .isEqualTo(installer)
+    }
+}
diff --git a/hostsidetests/media/Android.bp b/hostsidetests/media/Android.bp
index 3e85112..635c5c2 100644
--- a/hostsidetests/media/Android.bp
+++ b/hostsidetests/media/Android.bp
@@ -33,6 +33,10 @@
         "tradefed",
         "compatibility-host-util",
     ],
+
+    static_libs: [
+        "cts-host-utils",
+    ],
 }
 
 filegroup {
diff --git a/hostsidetests/media/bitstreams/Android.bp b/hostsidetests/media/bitstreams/Android.bp
index 9c17208..f13984e 100644
--- a/hostsidetests/media/bitstreams/Android.bp
+++ b/hostsidetests/media/bitstreams/Android.bp
@@ -22,6 +22,9 @@
         "cts-tradefed",
         "tradefed",
     ],
+    static_libs: [
+        "cts-host-utils",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
index 72bb15a..0c18703 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
index 5bcfe7e..6ca00d8 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
index 7db0198f..3dd9b35 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
index 6ba3822..7c06151 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
index 4811150..1634856 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
index b7f2c67..90e9bdf 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
index e00283f..50e9924 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
index eb0fda7..b7a752d 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
index 1334c82..7d8b289 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
index 0efdad2..528eefb 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
index 0f564ea..94bad70 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
index bbfc2eb..7146d94 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
index 8587abd..679850a 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
index d209011..656ea21 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
index 9b048bb..b80ff7f 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
index a03f1c4..3dda21a 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
index 22786fc..bf6b095 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/utils/Android.bp b/hostsidetests/utils/Android.bp
new file mode 100644
index 0000000..0930f91
--- /dev/null
+++ b/hostsidetests/utils/Android.bp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+java_library_host {
+    name: "cts-host-utils",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+    ],
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4ClassRunnerWithParameters.java b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4ClassRunnerWithParameters.java
similarity index 97%
rename from hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4ClassRunnerWithParameters.java
rename to hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4ClassRunnerWithParameters.java
index 944e258..7f8e07e 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4ClassRunnerWithParameters.java
+++ b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4ClassRunnerWithParameters.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.cts.bitstreams;
+package android.cts.host.utils;
 
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4Parameterized.java b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4Parameterized.java
similarity index 97%
rename from hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4Parameterized.java
rename to hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4Parameterized.java
index ea7ce7f..114678d 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4Parameterized.java
+++ b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4Parameterized.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.cts.bitstreams;
+package android.cts.host.utils;
 
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.ConfigurationException;