Merge "Remove sungsoo@google.com from tests/bluetooth/OWNERS" into main
diff --git a/hostsidetests/packageinstaller/Android.bp b/hostsidetests/packageinstaller/Android.bp
index d6e420f..136b9bc 100644
--- a/hostsidetests/packageinstaller/Android.bp
+++ b/hostsidetests/packageinstaller/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -25,7 +26,7 @@
         "tradefed",
         "truth",
     ],
-    data: [
+    device_common_data: [
         ":CtsRootPackageInstallerTestCases",
         ":CtsRootRollbackManagerHostTestHelperApp",
     ],
diff --git a/hostsidetests/rollback/Android.bp b/hostsidetests/rollback/Android.bp
index 3a26433..997c9f9 100644
--- a/hostsidetests/rollback/Android.bp
+++ b/hostsidetests/rollback/Android.bp
@@ -27,7 +27,7 @@
         "truth",
     ],
     static_libs: ["cts-install-lib-host"],
-    data: [":CtsRootRollbackManagerHostTestHelperApp"],
+    device_common_data: [":CtsRootRollbackManagerHostTestHelperApp"],
     test_suites: [
         "cts_root",
         "general-tests",
diff --git a/hostsidetests/rollback/AndroidTest.xml b/hostsidetests/rollback/AndroidTest.xml
index 35e7cda..89fd419 100644
--- a/hostsidetests/rollback/AndroidTest.xml
+++ b/hostsidetests/rollback/AndroidTest.xml
@@ -24,6 +24,10 @@
         <option name="test-file-name" value="CtsRootRollbackManagerHostTestHelperApp.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="auto-update-time" value="OFF"/>
+        <option name="auto-update-timezone" value="OFF"/>
+    </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="com.android.cts_root.rollback.host.RollbackManagerHostTest" />
     </test>
diff --git a/hostsidetests/rollback/src/com/android/cts_root/rollback/host/WatchdogEventLogger.java b/hostsidetests/rollback/src/com/android/cts_root/rollback/host/WatchdogEventLogger.java
index 59eb027..6aaaf61 100644
--- a/hostsidetests/rollback/src/com/android/cts_root/rollback/host/WatchdogEventLogger.java
+++ b/hostsidetests/rollback/src/com/android/cts_root/rollback/host/WatchdogEventLogger.java
@@ -19,46 +19,36 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
 
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.Truth;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class WatchdogEventLogger {
-    private static final String[] ROLLBACK_EVENT_TYPES = {
-            "ROLLBACK_INITIATE", "ROLLBACK_BOOT_TRIGGERED", "ROLLBACK_SUCCESS"};
-    private static final String[] ROLLBACK_EVENT_ATTRS = {
-            "logPackage", "rollbackReason", "failedPackageName"};
-    private static final String PROP_PREFIX = "persist.sys.rollbacktest.";
 
     private ITestDevice mDevice;
 
-    private void resetProperties(boolean enabled) throws Exception {
+    private void updateTestSysProp(boolean enabled) throws Exception {
         assertThat(mDevice.setProperty(
-                PROP_PREFIX + "enabled", String.valueOf(enabled))).isTrue();
-        for (String type : ROLLBACK_EVENT_TYPES) {
-            String key = PROP_PREFIX + type;
-            assertThat(mDevice.setProperty(key, "")).isTrue();
-            for (String attr : ROLLBACK_EVENT_ATTRS) {
-                assertThat(mDevice.setProperty(key + "." + attr, "")).isTrue();
-            }
-        }
+                "persist.sys.rollbacktest.enabled", String.valueOf(enabled))).isTrue();
     }
 
     public void start(ITestDevice device) throws Exception {
         mDevice = device;
-        resetProperties(true);
+        updateTestSysProp(true);
     }
 
     public void stop() throws Exception {
         if (mDevice != null) {
-            resetProperties(false);
+            updateTestSysProp(false);
         }
     }
 
-    private boolean matchProperty(String type, String attr, String expectedVal) throws Exception {
-        String key = PROP_PREFIX + type + "." + attr;
-        String val = mDevice.getProperty(key);
-        return expectedVal == null || expectedVal.equals(val);
+    private boolean verifyEventContainsVal(String watchdogEvent, String expectedVal) {
+        return expectedVal == null || watchdogEvent.contains(expectedVal);
     }
 
     /**
@@ -68,11 +58,33 @@
      * occurred, and return {@code true} if an event exists which matches all criteria.
      */
     public boolean watchdogEventOccurred(String type, String logPackage,
-            String rollbackReason, String failedPackageName) throws Exception {
-        return mDevice.getBooleanProperty(PROP_PREFIX + type, false)
-                && matchProperty(type, "logPackage", logPackage)
-                && matchProperty(type, "rollbackReason", rollbackReason)
-                && matchProperty(type, "failedPackageName", failedPackageName);
+            String rollbackReason, String failedPackageName) {
+        String watchdogEvent = getEventForRollbackType(type);
+        return (watchdogEvent != null)
+                && verifyEventContainsVal(watchdogEvent, logPackage)
+                && verifyEventContainsVal(watchdogEvent, rollbackReason)
+                && verifyEventContainsVal(watchdogEvent, failedPackageName);
+    }
+
+    /** Returns last matched event for rollbackType **/
+    private String getEventForRollbackType(String rollbackType) {
+        String lastMatchedEvent = null;
+        try {
+            String rollbackDump = mDevice.executeShellCommand("dumpsys rollback");
+            String eventRegex = ".*%s%s(.*)\\n";
+            String eventPrefix = "Watchdog event occurred with type: ";
+
+            final Pattern pattern = Pattern.compile(
+                    String.format(eventRegex, eventPrefix, rollbackType));
+            final Matcher matcher = pattern.matcher(rollbackDump);
+            while (matcher.find()) {
+                lastMatchedEvent = matcher.group(1);
+            }
+            CLog.d("Found watchdogEvent: " + lastMatchedEvent + " for type: " + rollbackType);
+        } catch (Exception e) {
+            CLog.e("Unable to find event for type: " + rollbackType, e);
+        }
+        return lastMatchedEvent;
     }
 
     static class Subject extends com.google.common.truth.Subject {
@@ -93,7 +105,7 @@
         }
 
         void eventOccurred(String type, String logPackage, String rollbackReason,
-                String failedPackageName) throws Exception {
+                String failedPackageName) {
             check("watchdogEventOccurred(type=%s, logPackage=%s, rollbackReason=%s, "
                     + "failedPackageName=%s)", type, logPackage, rollbackReason, failedPackageName)
                     .that(mActual.watchdogEventOccurred(type, logPackage, rollbackReason,
diff --git a/tests/bionic/Android.bp b/tests/bionic/Android.bp
new file mode 100644
index 0000000..62e8102
--- /dev/null
+++ b/tests/bionic/Android.bp
@@ -0,0 +1,14 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CtsBionicRootTestCases",
+
+    defaults: ["cts_bionic_defaults"],
+
+    test_suites: [
+        "cts_root",
+    ],
+}
+
diff --git a/tests/bionic/AndroidTest.xml b/tests/bionic/AndroidTest.xml
new file mode 100644
index 0000000..b645b76
--- /dev/null
+++ b/tests/bionic/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Bionic root test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="bionic" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <!-- TODO(b/126376458): Remove this when sharding is supported by libgtest_isolated -->
+    <option name="not-shardable" value="true" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsBionicRootTestCases->/data/local/tests/unrestricted/CtsBionicRootTestCases" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tests/unrestricted" />
+        <option name="module-name" value="CtsBionicRootTestCases" />
+        <option name="native-test-timeout" value="4m30s" />
+        <option name="test-case-timeout" value="4m30s" />
+        <option name="runtime-hint" value="12m10s" />
+    </test>
+</configuration>
diff --git a/tests/bionic/OWNERS b/tests/bionic/OWNERS
new file mode 100644
index 0000000..4ba8ede
--- /dev/null
+++ b/tests/bionic/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 14890
+include platform/bionic:/OWNERS
diff --git a/tests/bionic/README.md b/tests/bionic/README.md
new file mode 100644
index 0000000..807d40a
--- /dev/null
+++ b/tests/bionic/README.md
@@ -0,0 +1,10 @@
+# bionic tests
+
+This is the CTS-root boilerplate for the majority of the bionic tests. The
+actual gtests themselves are in bionic/tests/.
+
+See also cts/tests/tests/bionic_app for tests of libc functions in an
+app or JNI context.
+
+See also cts/tests/tests/dynamic_linker/ for some similar testing of
+the dynamic linker.
diff --git a/tests/bluetooth/Android.bp b/tests/bluetooth/Android.bp
index 6e117f1..ccb5125 100644
--- a/tests/bluetooth/Android.bp
+++ b/tests/bluetooth/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_bluetooth",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -27,8 +28,8 @@
         "statsd-helper",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.system",
+        "android.test.base.stubs.system",
         "statsdprotonano",
     ],
     srcs: ["src/**/*.java"],
@@ -39,6 +40,7 @@
         "cts_root",
         "general-tests",
         "mts-bluetooth",
+        "mts-bt",
     ],
     min_sdk_version: "UpsideDownCake",
 }
diff --git a/tests/bluetooth/AndroidTest.xml b/tests/bluetooth/AndroidTest.xml
index 87be477..97bf9d8 100644
--- a/tests/bluetooth/AndroidTest.xml
+++ b/tests/bluetooth/AndroidTest.xml
@@ -46,7 +46,7 @@
     <!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
     <object type="module_controller"
             class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.android.btservices" />
-        <option name="mainline-module-package-name" value="com.google.android.btservices" />
+        <option name="mainline-module-package-name" value="com.android.bt" />
+        <option name="mainline-module-package-name" value="com.google.android.bt" />
     </object>
 </configuration>
diff --git a/tests/bugreport/Android.bp b/tests/bugreport/Android.bp
index 3c2c368..7c70fef 100644
--- a/tests/bugreport/Android.bp
+++ b/tests/bugreport/Android.bp
@@ -26,8 +26,9 @@
         "truth",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.test",
+        "android.test.base.stubs.test",
+        "device_policy_aconfig_flags_lib",
     ],
     data: [":ctsroot-bugreport-artifacts"],
     srcs: ["src/**/*.java"],
diff --git a/tests/bugreport/OWNERS b/tests/bugreport/OWNERS
index fca58a6..0819864 100644
--- a/tests/bugreport/OWNERS
+++ b/tests/bugreport/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 153446
-gavincorkery@google.com
+ronish@google.com
 nandana@google.com
diff --git a/tests/bugreport/TEST_MAPPING b/tests/bugreport/TEST_MAPPING
index c37dda4..a4ed6fb 100644
--- a/tests/bugreport/TEST_MAPPING
+++ b/tests/bugreport/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsRootBugreportTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
+      "name": "CtsRootBugreportTestCases"
     }
   ],
   "postsubmit": [
diff --git a/tests/bugreport/src/android/bugreport/cts_root/BugreportManagerTest.java b/tests/bugreport/src/android/bugreport/cts_root/BugreportManagerTest.java
index e85de59..53f351d 100644
--- a/tests/bugreport/src/android/bugreport/cts_root/BugreportManagerTest.java
+++ b/tests/bugreport/src/android/bugreport/cts_root/BugreportManagerTest.java
@@ -16,17 +16,27 @@
 
 package android.bugreport.cts_root;
 
+import static android.app.admin.flags.Flags.FLAG_ONBOARDING_BUGREPORT_STORAGE_BUG_FIX;
+import static android.app.admin.flags.Flags.FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS;
+
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.BugreportManager;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.BugreportParams;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
@@ -38,8 +48,12 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import org.junit.After;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -47,6 +61,8 @@
 
 import java.io.File;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -66,16 +82,41 @@
 
     private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
 
+    private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(4);
+    private static final int MAX_ALLOWED_BUGREPROTS = 8;
+    private static final String INTENT_BUGREPORT_FINISHED =
+            "com.android.internal.intent.action.BUGREPORT_FINISHED";
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+
     @Before
-    public void setup() {
+    public void setup() throws Exception {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mBugreportManager = mContext.getSystemService(BugreportManager.class);
+        ensureNoConsentDialogShown();
+
+
+        // Unlock before finding/clicking an object.
+        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        device.wakeUp();
+        device.executeShellCommand("wm dismiss-keyguard");
+    }
+
+    @BeforeClass
+    public static void classSetup() {
+        runShellCommand("settings put global auto_time 0");
+        runShellCommand("svc power stayon true");
         // Kill current bugreport, so that it does not interfere with future bugreports.
         runShellCommand("setprop ctl.stop bugreportd");
     }
 
-    @After
-    public void tearDown() {
+    @AfterClass
+    public static void classTearDown() {
+        // Restore auto time
+        runShellCommand("settings put global auto_time 1");
+        runShellCommand("svc power stayon false");
         // Kill current bugreport, so that it does not interfere with future bugreports.
         runShellCommand("setprop ctl.stop bugreportd");
     }
@@ -83,88 +124,368 @@
     @LargeTest
     @Test
     public void testRetrieveBugreportConsentGranted() throws Exception {
-        File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
-        File startBugreportFile = createTempFile("startbugreport", ".zip");
-        CountDownLatch latch = new CountDownLatch(1);
-        BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
-        mBugreportManager.startBugreport(parcelFd(startBugreportFile), null,
-                new BugreportParams(
-                        BugreportParams.BUGREPORT_MODE_INTERACTIVE,
-                        BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
-                mContext.getMainExecutor(), callback);
-        latch.await(4, TimeUnit.MINUTES);
-        assertThat(callback.isSuccess()).isTrue();
-        // No data should be passed to the FD used to call startBugreport.
-        assertThat(startBugreportFile.length()).isEqualTo(0);
-        String bugreportFileLocation = callback.getBugreportFile();
-        waitForDumpstateServiceToStop();
+        try {
+            ensureNotConsentlessReport();
+            File startBugreportFile = createTempFile("startbugreport", ".zip");
+            CountDownLatch latch = new CountDownLatch(1);
+            BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
+            mBugreportManager.startBugreport(parcelFd(startBugreportFile), null,
+                    new BugreportParams(
+                            BugreportParams.BUGREPORT_MODE_ONBOARDING,
+                            BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
+                    mContext.getMainExecutor(), callback);
+            latch.await(4, TimeUnit.MINUTES);
+            assertThat(callback.isSuccess()).isTrue();
+            // No data should be passed to the FD used to call startBugreport.
+            assertThat(startBugreportFile.length()).isEqualTo(0);
+            String bugreportFileLocation = callback.getBugreportFile();
+            waitForDumpstateServiceToStop();
 
+            // Trying to retrieve an unknown bugreport should fail
+            latch = new CountDownLatch(1);
+            callback = new BugreportCallbackImpl(latch);
+            File bugreportFile2 = createTempFile("bugreport2_" + name.getMethodName(), ".zip");
+            mBugreportManager.retrieveBugreport(
+                    "unknown/file.zip", parcelFd(bugreportFile2),
+                    mContext.getMainExecutor(), callback);
+            assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
+            assertThat(callback.getErrorCode()).isEqualTo(
+                    BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+            waitForDumpstateServiceToStop();
 
-
-        // Trying to retrieve an unknown bugreport should fail
-        latch = new CountDownLatch(1);
-        callback = new BugreportCallbackImpl(latch);
-        File bugreportFile2 = createTempFile("bugreport2_" + name.getMethodName(), ".zip");
-        mBugreportManager.retrieveBugreport(
-                "unknown/file.zip", parcelFd(bugreportFile2),
-                mContext.getMainExecutor(), callback);
-        assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
-        assertThat(callback.getErrorCode()).isEqualTo(
-                BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
-
-        // A bugreport was previously generated for this caller. When the consent dialog is invoked
-        // and accepted, the bugreport files should be passed to the calling package.
-        ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile);
-        assertThat(bugreportFd).isNotNull();
-        latch = new CountDownLatch(1);
-        mBugreportManager.retrieveBugreport(bugreportFileLocation, bugreportFd,
-                mContext.getMainExecutor(), new BugreportCallbackImpl(latch));
-        shareConsentDialog(ConsentReply.ALLOW);
-        assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
-        assertThat(bugreportFile.length()).isGreaterThan(0);
+            File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
+            // A bugreport was previously generated for this caller. When the consent dialog is invoked
+            // and accepted, the bugreport files should be passed to the calling package.
+            ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile);
+            assertThat(bugreportFd).isNotNull();
+            latch = new CountDownLatch(1);
+            mBugreportManager.retrieveBugreport(bugreportFileLocation, bugreportFd,
+                    mContext.getMainExecutor(), new BugreportCallbackImpl(latch));
+            shareConsentDialog(ConsentReply.ALLOW);
+            assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
+            assertThat(bugreportFile.length()).isGreaterThan(0);
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
     }
 
 
     @LargeTest
     @Test
     public void testRetrieveBugreportConsentDenied() throws Exception {
-        File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
+        try {
+            // User denies consent, therefore no data should be passed back to the bugreport file.
+            ensureNotConsentlessReport();
+            CountDownLatch latch = new CountDownLatch(1);
+            BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
+            mBugreportManager.startBugreport(parcelFd(new File("/dev/null")),
+                    null, new BugreportParams(BugreportParams.BUGREPORT_MODE_ONBOARDING,
+                            BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
+                    mContext.getMainExecutor(), callback);
+            latch.await(4, TimeUnit.MINUTES);
+            assertThat(callback.isSuccess()).isTrue();
+            String bugreportFileLocation = callback.getBugreportFile();
+            waitForDumpstateServiceToStop();
 
-        // User denies consent, therefore no data should be passed back to the bugreport file.
+            latch = new CountDownLatch(1);
+            callback = new BugreportCallbackImpl(latch);
+            File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
+            ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile);
+            assertThat(bugreportFd).isNotNull();
+            mBugreportManager.retrieveBugreport(
+                    bugreportFileLocation,
+                    bugreportFd,
+                    mContext.getMainExecutor(),
+                    callback);
+            shareConsentDialog(ConsentReply.DENY);
+            latch.await(1, TimeUnit.MINUTES);
+            assertThat(callback.getErrorCode()).isEqualTo(
+                    BugreportCallback.BUGREPORT_ERROR_USER_DENIED_CONSENT);
+            assertThat(bugreportFile.length()).isEqualTo(0);
+            waitForDumpstateServiceToStop();
+
+            // Since consent has already been denied, this call should fail because consent cannot
+            // be requested twice for the same bugreport.
+            latch = new CountDownLatch(1);
+            callback = new BugreportCallbackImpl(latch);
+            mBugreportManager.retrieveBugreport(bugreportFileLocation, parcelFd(bugreportFile),
+                    mContext.getMainExecutor(), callback);
+            latch.await(1, TimeUnit.MINUTES);
+            assertThat(callback.getErrorCode()).isEqualTo(
+                    BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+            waitForDumpstateServiceToStop();
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
+    }
+
+    @LargeTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_ONBOARDING_BUGREPORT_STORAGE_BUG_FIX)
+    public void testBugreportsLimitReached() throws Exception {
+        try {
+            List<File> bugreportFiles = new ArrayList<>();
+            List<String> bugreportFileLocations = new ArrayList<>();
+            CountDownLatch latch = new CountDownLatch(1);
+
+            for (int i = 0; i < MAX_ALLOWED_BUGREPROTS + 1; i++) {
+                waitForDumpstateServiceToStop();
+                File bugreportFile = createTempFile(
+                        "bugreport_" + name.getMethodName() + "_" + i, ".zip");
+                bugreportFiles.add(bugreportFile);
+                File startBugreportFile = createTempFile("startbugreport", ".zip");
+
+                latch = new CountDownLatch(1);
+                BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
+
+                mBugreportManager.startBugreport(parcelFd(startBugreportFile), null,
+                        new BugreportParams(
+                                BugreportParams.BUGREPORT_MODE_ONBOARDING,
+                                BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
+                        mContext.getMainExecutor(), callback);
+
+                latch.await(BUGREPORT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                assertThat(callback.isSuccess()).isTrue();
+                bugreportFileLocations.add(callback.getBugreportFile());
+                waitForDumpstateServiceToStop();
+            }
+
+            final long newTime = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10);
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    mContext.getSystemService(AlarmManager.class).setTime(newTime));
+
+            // Trigger a shell bugreport to trigger cleanup logic
+            triggerShellBugreport(BugreportParams.BUGREPORT_MODE_ONBOARDING);
+
+            // The retrieved first bugreport file should be empty.
+            latch = new CountDownLatch(1);
+            BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
+            mBugreportManager.retrieveBugreport(
+                    bugreportFileLocations.getFirst(), parcelFd(bugreportFiles.getFirst()),
+                    mContext.getMainExecutor(), callback);
+            ensureNotConsentlessReport();
+            shareConsentDialog(ConsentReply.ALLOW);
+            assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
+            assertThat(bugreportFiles.getFirst().length()).isEqualTo(0);
+            waitForDumpstateServiceToStop();
+
+            // The retrieved last bugreport file should not be empty.
+            latch = new CountDownLatch(1);
+            callback = new BugreportCallbackImpl(latch);
+            mBugreportManager.retrieveBugreport(
+                    bugreportFileLocations.getLast(), parcelFd(bugreportFiles.getLast()),
+                    mContext.getMainExecutor(), callback);
+            ensureNotConsentlessReport();
+            shareConsentDialog(ConsentReply.ALLOW);
+            assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
+            assertThat(bugreportFiles.getLast().length()).isGreaterThan(0);
+            waitForDumpstateServiceToStop();
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
+    }
+
+    @LargeTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS)
+    public void testBugreport_skipsConsentForDeferredReportAfterFullReport() throws Exception {
+        try {
+            ensureNotConsentlessReport();
+            startFullReport(false);
+
+            startDeferredReport(true);
+            startDeferredReport(true);
+
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
+    }
+
+    @LargeTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS)
+    public void testBugreport_skipConsentForDeferredReportAfterDeferredReport() throws Exception {
+        try {
+            ensureNotConsentlessReport();
+            startDeferredReport(false);
+
+            startDeferredReport(true);
+
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
+    }
+
+    @LargeTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS)
+    @Ignore("b/344704922")
+    public void testBugreport_doesNotSkipConsentForFullReportAfterFullReport() throws Exception {
+        try {
+            ensureNotConsentlessReport();
+            startFullReport(false);
+
+            startFullReport(false);
+
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
+    }
+
+    @LargeTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS)
+    public void testBugreport_skipConsentForFullReportAfterDeferredReport() throws Exception {
+        try {
+            ensureNotConsentlessReport();
+            startDeferredReport(false);
+
+            startFullReport(true);
+
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
+    }
+
+    @LargeTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS)
+    public void testBugreport_doesNotSkipConsentAfterTimeLimit() throws Exception {
+        try {
+            ensureNotConsentlessReport();
+            startFullReport(false);
+            final long newTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(3);
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    mContext.getSystemService(AlarmManager.class).setTime(newTime));
+
+            startDeferredReport(false);
+
+        } finally {
+            waitForDumpstateServiceToStop();
+            // Remove all bugreport files
+            SystemUtil.runShellCommand("rm -f -rR -v /bugreports/");
+        }
+    }
+
+    private void ensureNotConsentlessReport() {
+        final long time = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(60);
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                mContext.getSystemService(AlarmManager.class).setTime(time));
+        assertThat(System.currentTimeMillis()).isGreaterThan(time);
+    }
+
+    private void startFullReport(boolean skipConsent) throws Exception {
+        waitForDumpstateServiceToStop();
+        File bugreportFile = createTempFile("startbugreport", ".zip");
         CountDownLatch latch = new CountDownLatch(1);
         BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
-        mBugreportManager.startBugreport(parcelFd(new File("/dev/null")),
-                null, new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE,
-                BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
+        mBugreportManager.startBugreport(parcelFd(bugreportFile), null,
+                new BugreportParams(BugreportParams.BUGREPORT_MODE_ONBOARDING, 0),
                 mContext.getMainExecutor(), callback);
-        latch.await(4, TimeUnit.MINUTES);
+        if (!skipConsent) {
+            shareConsentDialog(ConsentReply.ALLOW);
+        }
+
+        latch.await(2, TimeUnit.MINUTES);
         assertThat(callback.isSuccess()).isTrue();
-        String bugreportFileLocation = callback.getBugreportFile();
+        // No data should be passed to the FD used to call startBugreport.
+        assertThat(bugreportFile.length()).isGreaterThan(0);
+        waitForDumpstateServiceToStop();
+    }
+
+    private void startDeferredReport(boolean skipConsent) throws Exception {
+        waitForDumpstateServiceToStop();
+        File bugreportFile = createTempFile("startbugreport", ".zip");
+        CountDownLatch latch = new CountDownLatch(1);
+        BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
+        mBugreportManager.startBugreport(parcelFd(bugreportFile), null,
+                new BugreportParams(
+                        BugreportParams.BUGREPORT_MODE_ONBOARDING,
+                        BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
+                mContext.getMainExecutor(), callback);
+
+        latch.await(1, TimeUnit.MINUTES);
+        assertThat(callback.isSuccess()).isTrue();
+        String location = callback.getBugreportFile();
         waitForDumpstateServiceToStop();
 
-        latch = new CountDownLatch(1);
-        ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile);
-        assertThat(bugreportFd).isNotNull();
-        mBugreportManager.retrieveBugreport(
-                bugreportFileLocation,
-                bugreportFd,
-                mContext.getMainExecutor(),
-                callback);
-        shareConsentDialog(ConsentReply.DENY);
-        latch.await(1, TimeUnit.MINUTES);
-        assertThat(callback.getErrorCode()).isEqualTo(
-                BugreportCallback.BUGREPORT_ERROR_USER_DENIED_CONSENT);
-        assertThat(bugreportFile.length()).isEqualTo(0);
 
-        // Since consent has already been denied, this call should fail because consent cannot
-        // be requested twice for the same bugreport.
+        // The retrieved bugreport file should not be empty.
         latch = new CountDownLatch(1);
         callback = new BugreportCallbackImpl(latch);
-        mBugreportManager.retrieveBugreport(bugreportFileLocation, parcelFd(bugreportFile),
+        mBugreportManager.retrieveBugreport(
+                location, parcelFd(bugreportFile),
                 mContext.getMainExecutor(), callback);
-        latch.await(1, TimeUnit.MINUTES);
-        assertThat(callback.getErrorCode()).isEqualTo(
-                BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+        if (!skipConsent) {
+            shareConsentDialog(ConsentReply.ALLOW);
+        }
+        assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
+        assertThat(bugreportFile.length()).isGreaterThan(0);
+        waitForDumpstateServiceToStop();
+    }
+
+    private void triggerShellBugreport(int type) throws Exception {
+        BugreportBroadcastReceiver br = new BugreportBroadcastReceiver();
+        final IntentFilter intentFilter = new IntentFilter(INTENT_BUGREPORT_FINISHED);
+        mContext.registerReceiver(br, intentFilter, Context.RECEIVER_EXPORTED);
+        final BugreportParams params = new BugreportParams(type);
+        mBugreportManager.requestBugreport(params, "" /* shareTitle */, "" /* shareDescription */);
+
+        try {
+            br.waitForBugreportFinished();
+        } finally {
+            // The latch may fail for a number of reasons but we still need to unregister the
+            // BroadcastReceiver.
+            mContext.unregisterReceiver(br);
+        }
+
+        Intent response = br.getBugreportFinishedIntent();
+        assertThat(response.getAction()).isEqualTo(intentFilter.getAction(0));
+        waitForDumpstateServiceToStop();
+    }
+
+    private class BugreportBroadcastReceiver extends BroadcastReceiver {
+        Intent bugreportFinishedIntent = null;
+        final CountDownLatch latch;
+
+        BugreportBroadcastReceiver() {
+            latch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            setBugreportFinishedIntent(intent);
+            latch.countDown();
+        }
+
+        private void setBugreportFinishedIntent(Intent intent) {
+            bugreportFinishedIntent = intent;
+        }
+
+        public Intent getBugreportFinishedIntent() {
+            return bugreportFinishedIntent;
+        }
+
+        public void waitForBugreportFinished() throws Exception {
+            if (!latch.await(BUGREPORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                throw new Exception("Failed to receive BUGREPORT_FINISHED in "
+                        + BUGREPORT_TIMEOUT_MS + " ms.");
+            }
+        }
     }
 
     private ParcelFileDescriptor parcelFd(File file) throws Exception {
@@ -250,10 +571,6 @@
     private void shareConsentDialog(@NonNull ConsentReply consentReply) throws Exception {
         final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 
-        // Unlock before finding/clicking an object.
-        device.wakeUp();
-        device.executeShellCommand("wm dismiss-keyguard");
-
         final BySelector consentTitleObj = By.res("android", "alertTitle");
         if (!device.wait(Until.hasObject(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)) {
             fail("The consent dialog is not found");
@@ -274,20 +591,46 @@
         assertThat(device.wait(Until.gone(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)).isTrue();
     }
 
+    /*
+     * Ensure the consent dialog is shown and take action according to <code>consentReply<code/>.
+     * It will fail if the dialog is not shown when <code>ignoreNotFound<code/> is false.
+     */
+    private void ensureNoConsentDialogShown() throws Exception {
+        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        final BySelector consentTitleObj = By.res("android", "alertTitle");
+        if (!device.wait(Until.hasObject(consentTitleObj), TimeUnit.SECONDS.toMillis(2))) {
+            return;
+        }
+        final BySelector selector = By.res("android", "button2");
+        final UiObject2 btnObj = device.findObject(selector);
+        if (btnObj == null) {
+            return;
+        }
+        btnObj.click();
+
+        device.wait(Until.gone(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS);
+    }
+
 
     /** Waits for the dumpstate service to stop, for up to 5 seconds. */
     private void waitForDumpstateServiceToStop() throws Exception {
         int pollingIntervalMillis = 100;
-        int numPolls = 50;
         Method method = Class.forName("android.os.ServiceManager").getMethod(
                 "getService", String.class);
-        while (numPolls-- > 0) {
-            // If getService() returns null, the service has stopped.
-            if (method.invoke(null, "dumpstate") == null) {
-                return;
+        for (int i = 0; i < 10; i++) {
+            int numPolls = 50;
+            while (numPolls-- > 0) {
+                // If getService() returns null, the service has stopped.
+                if (method.invoke(null, "dumpstate") == null) {
+                    break;
+                }
+                Thread.sleep(pollingIntervalMillis);
             }
-            Thread.sleep(pollingIntervalMillis);
         }
-        fail("Dumpstate did not stop within 5 seconds");
+        if (method.invoke(null, "dumpstate") == null) {
+            return;
+        }
+        fail("Dumpstate did not stop within 25 seconds");
     }
 }
diff --git a/tests/input/Android.bp b/tests/input/Android.bp
new file mode 100644
index 0000000..52c94d5
--- /dev/null
+++ b/tests/input/Android.bp
@@ -0,0 +1,57 @@
+// Copyright 2024 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_team: "trendy_team_input_framework",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsInputRootTestCases",
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts_root",
+        "general-tests",
+    ],
+
+    compile_multilib: "both",
+    kotlincflags: [
+        "-Werror",
+    ],
+    srcs: [
+        "src/**/*.kt",
+    ],
+    asset_dirs: ["assets"],
+    static_libs: [
+        "cts-input-lib",
+        "CtsVirtualDeviceCommonLib",
+        "android.view.flags-aconfig-java",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "bedstead-root-annotations",
+        "com.android.hardware.input-aconfig-java",
+        "com.android.input.flags-aconfig-java",
+        "compatibility-device-util-axt",
+        "cts-input-lib",
+        "cts-wm-util",
+        "flag-junit",
+        "kotlin-test",
+        "ui-trace-collector",
+        "collector-device-lib",
+        "platform-screenshot-diff-core",
+    ],
+    sdk_version: "test_current",
+    per_testcase_directory: true,
+}
diff --git a/tests/input/AndroidManifest.xml b/tests/input/AndroidManifest.xml
new file mode 100644
index 0000000..47b55be
--- /dev/null
+++ b/tests/input/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2024 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.input.cts_root">
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <application android:label="InputTest"
+                 android:requestLegacyExternalStorage="true">
+        <activity android:name="android.input.cts_root.CaptureEventActivity"
+                  android:label="Capture events"
+                  android:configChanges="touchscreen|uiMode|orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
+                  android:enableOnBackInvokedCallback="false"
+                  android:turnScreenOn="true"
+                  android:exported="true">
+        </activity>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.input.cts_root"
+         android:label="Tests for input APIs and behaviours.">
+    </instrumentation>
+</manifest>
diff --git a/tests/input/AndroidTest.xml b/tests/input/AndroidTest.xml
new file mode 100644
index 0000000..fa5797e
--- /dev/null
+++ b/tests/input/AndroidTest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS-root input test cases">
+    <option name="test-suite-tag" value="cts_root" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
+    <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" />
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsInputRootTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.input.cts_root" />
+        <option name="runtime-hint" value="14s" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
+        <option name="device-listeners" value="android.device.collectors.ScreenshotOnFailureCollector"/>
+        <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/>
+        <!-- DefaultUITraceListener args -->
+        <option name="instrumentation-arg" key="skip_test_success_metrics" value="true"/>
+    </test>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- TODO(b/285554134): Remove once underlying issue is fixed-->
+        <option name="run-command" value="wm set-ignore-orientation-request false" />
+        <option name="run-command" value="wm set-letterbox-style --isEducationEnabled false" />
+        <!-- Unlock screen -->
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <!-- Dismiss keyguard, in case it's set as "Swipe to unlock" -->
+        <option name="run-command" value="wm dismiss-keyguard" />
+        <!-- Collapse notifications -->
+        <option name="run-command" value="cmd statusbar collapse" />
+        <!-- dismiss all system dialogs before launch test -->
+        <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS" />
+    </target_preparer>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="input_.*" />
+        <!-- Pull perfetto traces from DefaultUITraceListener -->
+        <option name="pull-pattern-keys" value="perfetto_file_path*" />
+        <!-- Pull screenshot on test failure -->
+        <option name="pull-pattern-keys"
+            value="android.device.collectors.ScreenshotOnFailureCollector.*\.png" />
+        <!-- Pull files created by tests, like the output of screenshot tests -->
+        <option name="directory-keys" value="/sdcard/Download/CtsInputRootTestCases" />
+        <option name="collect-on-run-ended-only" value="false" />
+    </metrics_collector>
+</configuration>
diff --git a/tests/input/OWNERS b/tests/input/OWNERS
new file mode 100644
index 0000000..21d208f
--- /dev/null
+++ b/tests/input/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 136048
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/tests/input/TEST_MAPPING b/tests/input/TEST_MAPPING
new file mode 100644
index 0000000..cb36f58
--- /dev/null
+++ b/tests/input/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "CtsInputRootTestCases"
+    }
+  ]
+}
diff --git a/tests/input/assets/testHidePointerIconOnSecureWindowScreenshot_DRAWING_TABLET_expected.png b/tests/input/assets/testHidePointerIconOnSecureWindowScreenshot_DRAWING_TABLET_expected.png
new file mode 100644
index 0000000..2774392
--- /dev/null
+++ b/tests/input/assets/testHidePointerIconOnSecureWindowScreenshot_DRAWING_TABLET_expected.png
Binary files differ
diff --git a/tests/input/assets/testHidePointerIconOnSecureWindowScreenshot_MOUSE_expected.png b/tests/input/assets/testHidePointerIconOnSecureWindowScreenshot_MOUSE_expected.png
new file mode 100644
index 0000000..2774392
--- /dev/null
+++ b/tests/input/assets/testHidePointerIconOnSecureWindowScreenshot_MOUSE_expected.png
Binary files differ
diff --git a/tests/input/src/android/input/cts_root/CaptureEventActivity.kt b/tests/input/src/android/input/cts_root/CaptureEventActivity.kt
new file mode 100644
index 0000000..246e6af
--- /dev/null
+++ b/tests/input/src/android/input/cts_root/CaptureEventActivity.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 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.input.cts_root
+
+import android.app.Activity
+import android.view.InputEvent
+import android.view.KeyEvent
+import android.view.MotionEvent
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+class CaptureEventActivity : Activity() {
+    private val events = LinkedBlockingQueue<InputEvent>()
+
+    override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
+        events.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+        events.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
+        events.add(KeyEvent(event))
+        return true
+    }
+
+    override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
+        events.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    fun getInputEvent(): InputEvent? {
+        return events.poll(5, TimeUnit.SECONDS)
+    }
+}
diff --git a/tests/input/src/android/input/cts_root/HidePointerIconOnSecureWindowScreenshotTest.kt b/tests/input/src/android/input/cts_root/HidePointerIconOnSecureWindowScreenshotTest.kt
new file mode 100644
index 0000000..471084b
--- /dev/null
+++ b/tests/input/src/android/input/cts_root/HidePointerIconOnSecureWindowScreenshotTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2024 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.input.cts_root
+
+import android.cts.input.EventVerifier
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.os.SystemProperties
+import android.platform.test.annotations.EnableFlags
+import android.view.MotionEvent
+import android.view.WindowManager
+import android.virtualdevice.cts.common.FakeAssociationRule
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.input.DefaultPointerSpeedRule
+import com.android.cts.input.TestPointerDevice
+import com.android.cts.input.VirtualDisplayActivityScenario
+import com.android.cts.input.inputeventmatchers.withMotionAction
+import com.android.input.flags.Flags
+import com.android.xts.root.annotations.RequireAdbRoot
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import platform.test.screenshot.GoldenPathManager
+import platform.test.screenshot.PathConfig
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.assertAgainstGolden
+import platform.test.screenshot.matchers.AlmostPerfectMatcher
+import platform.test.screenshot.matchers.BitmapMatcher
+import kotlin.test.assertNotNull
+import org.junit.Ignore
+
+/**
+ * End-to-end tests for the hiding pointer icons of screenshots of secure displays
+ *
+ * We use a secure virtual display to launch the test activity, and use virtual Input devices to
+ * move the pointer for it to show up. We then take a screenshot of the display to ensure the icon
+ * does not shows up on screenshot. We use the virtual display to be able to precisely compare the
+ * screenshots across devices of various form factors and sizes.
+ *
+ * Following tests must be run as root as they require CAPTURE_SECURE_VIDEO_OUTPUT permission
+ * override which can only be done by root.
+ */
+@MediumTest
+@RunWith(Parameterized::class)
+@RequireAdbRoot
+class HidePointerIconOnSecureWindowScreenshotTest {
+    private lateinit var activity: CaptureEventActivity
+    private lateinit var verifier: EventVerifier
+    private lateinit var exactScreenshotMatcher: BitmapMatcher
+
+    @get:Rule
+    val testName = TestName()
+    @get:Rule
+    val virtualDisplayRule = VirtualDisplayActivityScenario.Rule<CaptureEventActivity>(
+        testName,
+        useSecureDisplay = true,
+    )
+    @get:Rule
+    val fakeAssociationRule = FakeAssociationRule()
+    @get:Rule
+    val defaultPointerSpeedRule = DefaultPointerSpeedRule()
+    @get:Rule
+    val screenshotRule = ScreenshotTestRule(GoldenPathManager(
+        InstrumentationRegistry.getInstrumentation().context,
+        ASSETS_PATH,
+        TEST_OUTPUT_PATH,
+        PathConfig()
+    ), disableIconPool = false)
+
+    @Parameter(0)
+    lateinit var device: TestPointerDevice
+
+    @Before
+    fun setUp() {
+        val context = InstrumentationRegistry.getInstrumentation().targetContext
+        activity = virtualDisplayRule.activity
+        activity.runOnUiThread {
+            activity.actionBar?.hide()
+            activity.window.decorView.rootView.setBackgroundColor(Color.WHITE)
+            activity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        device.setUp(
+            context,
+            virtualDisplayRule.virtualDisplay.display,
+            fakeAssociationRule.associationInfo,
+        )
+
+        verifier = EventVerifier(activity::getInputEvent)
+
+        exactScreenshotMatcher =
+            AlmostPerfectMatcher(acceptableThresholdCount = MAX_PIXELS_DIFFERENT)
+    }
+
+    @After
+    fun tearDown() {
+        device.tearDown()
+    }
+
+    @Ignore("b/366475909")
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_POINTER_INDICATORS_FOR_SECURE_WINDOWS)
+    fun testHidePointerIconOnSecureWindowScreenshot() {
+        device.hoverMove(1, 1)
+        verifier.assertReceivedMotion(withMotionAction(MotionEvent.ACTION_HOVER_ENTER))
+        waitForPointerIconUpdate()
+
+        assertScreenshotsMatch()
+    }
+
+    private fun getActualScreenshot(): Bitmap {
+        val actualBitmap: Bitmap? = virtualDisplayRule.getScreenshot()
+        assertNotNull(actualBitmap, "Screenshot is null.")
+        return actualBitmap
+    }
+
+    private fun assertScreenshotsMatch() {
+        getActualScreenshot().assertAgainstGolden(
+            screenshotRule,
+            getParameterizedExpectedScreenshotName(),
+            exactScreenshotMatcher
+        )
+    }
+
+    private fun getParameterizedExpectedScreenshotName(): String {
+        // Replace illegal characters '[' and ']' in expected screenshot name with underscores.
+        return "${testName.methodName}expected".replace("""\[|\]""".toRegex(), "_")
+    }
+
+    // We don't have a way to synchronously know when the requested pointer icon has been drawn
+    // to the display, so wait some time (at least one display frame) for the icon to propagate.
+    private fun waitForPointerIconUpdate() = Thread.sleep(500L * HW_TIMEOUT_MULTIPLIER)
+
+    companion object {
+        const val MAX_PIXELS_DIFFERENT = 5
+        const val ASSETS_PATH = "tests/input/assets"
+        val TEST_OUTPUT_PATH =
+            "/sdcard/Download/CtsInputRootTestCases/" +
+            HidePointerIconOnSecureWindowScreenshotTest::class.java.simpleName
+        val HW_TIMEOUT_MULTIPLIER = SystemProperties.getInt("ro.hw_timeout_multiplier", 1);
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun data(): Iterable<Any> =
+            listOf(TestPointerDevice.MOUSE, TestPointerDevice.DRAWING_TABLET)
+    }
+}
diff --git a/tests/packagemanagerlocal/Android.bp b/tests/packagemanagerlocal/Android.bp
index a1c58e8..0202f4e 100644
--- a/tests/packagemanagerlocal/Android.bp
+++ b/tests/packagemanagerlocal/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -27,8 +28,8 @@
         "truth",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.test",
+        "android.test.base.stubs.test",
     ],
     srcs: ["src/**/*.java"],
     test_suites: [
diff --git a/tests/packagemanagerlocal/OWNERS b/tests/packagemanagerlocal/OWNERS
index 4e5fe55..54f11a0 100644
--- a/tests/packagemanagerlocal/OWNERS
+++ b/tests/packagemanagerlocal/OWNERS
@@ -1,3 +1,2 @@
 # Bug component: 36137
-alexbuy@google.com
 patb@google.com
diff --git a/tests/packagewatchdog/Android.bp b/tests/packagewatchdog/Android.bp
index 5c65280..6bc2d6c 100644
--- a/tests/packagewatchdog/Android.bp
+++ b/tests/packagewatchdog/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_mainline_modularization",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -27,8 +28,8 @@
         "platform-test-annotations",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.test",
+        "android.test.base.stubs.test",
     ],
     srcs: ["src/**/*.java"],
     test_suites: [
diff --git a/tests/packagewatchdog/OWNERS b/tests/packagewatchdog/OWNERS
index b929d27..86b12cd 100644
--- a/tests/packagewatchdog/OWNERS
+++ b/tests/packagewatchdog/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 1306443
-ancr@google.com
 harshitmahajan@google.com
 wangchun@google.com
\ No newline at end of file
diff --git a/tests/packagewatchdog/src/android/packagewatchdog/cts_root/PackageWatchdogTest.java b/tests/packagewatchdog/src/android/packagewatchdog/cts_root/PackageWatchdogTest.java
index d8b9b42..503b5c0 100644
--- a/tests/packagewatchdog/src/android/packagewatchdog/cts_root/PackageWatchdogTest.java
+++ b/tests/packagewatchdog/src/android/packagewatchdog/cts_root/PackageWatchdogTest.java
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package android.packagewatchdog.cts_root;
+package android.packagewatchdog.cts;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
 import android.content.pm.VersionedPackage;
+import android.test.UiThreadTest;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -52,6 +53,7 @@
     private TestObserver mTestObserver1, mTestObserver2;
 
     @Before
+    @UiThreadTest
     public void setUp() {
         Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mPackageWatchdog = PackageWatchdog.getInstance(mContext);
@@ -178,9 +180,9 @@
 
         mPackageWatchdog.startObservingHealth(mTestObserver1, Arrays.asList(APP_A), SHORT_DURATION);
 
-        mPackageWatchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+        raiseFatalFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-        mPackageWatchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
+        raiseFatalFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
 
         assertThat(mLatch1.await(5, TimeUnit.SECONDS)).isTrue();
@@ -237,6 +239,12 @@
         for (int i = 0; i < failureCount; i++) {
             mPackageWatchdog.onPackageFailure(failingPackages, failureReason);
         }
+        try {
+            // Wait for DEFAULT_MITIGATION_WINDOW_MS before applying another mitigation
+            Thread.sleep(TimeUnit.SECONDS.toMillis(6));
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
     }
 
     private static class TestObserver implements PackageWatchdog.PackageHealthObserver {
@@ -273,7 +281,7 @@
             return true;
         }
 
-        public String getName() {
+        public String getUniqueIdentifier() {
             return mName;
         }
 
diff --git a/tests/permission/Android.bp b/tests/permission/Android.bp
new file mode 100644
index 0000000..fd69606
--- /dev/null
+++ b/tests/permission/Android.bp
@@ -0,0 +1,54 @@
+// Copyright (C) 2024 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"],
+    default_team: "trendy_team_android_permissions",
+}
+
+android_test {
+    name: "CtsRootPermissionTestCases",
+    defaults: ["cts_defaults"],
+    data: [
+        ":CtsRootPermissionSignaturePermissionAllowlistNormalApp",
+    ],
+    libs: [
+        "android.test.runner.stubs.system",
+        "android.test.base.stubs.system",
+        "bedstead-root-annotations",
+    ],
+    min_sdk_version: "30",
+    // TODO(b/326241209): @TestApi isn't supported in system server right now.
+    //sdk_version: "test_current",
+    platform_apis: true,
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "flag-junit",
+        "platform-test-annotations",
+        "services.core",
+        "truth",
+    ],
+    target_sdk_version: "35",
+    test_suites: [
+        "cts_root",
+        // For gts-root before cts-root is required.
+        "gts",
+        "general-tests",
+    ],
+}
diff --git a/tests/permission/AndroidManifest.xml b/tests/permission/AndroidManifest.xml
new file mode 100644
index 0000000..514b34c
--- /dev/null
+++ b/tests/permission/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ Copyright (C) 2024 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.permission.cts_root" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+         android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android"
+         android:label="Permission CTS root tests" />
+</manifest>
diff --git a/tests/permission/AndroidTest.xml b/tests/permission/AndroidTest.xml
new file mode 100644
index 0000000..5ce9d32
--- /dev/null
+++ b/tests/permission/AndroidTest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2024 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="Runs Permission CTS root tests">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <option name="test-suite-tag" value="gts" />
+    <option name="config-descriptor:metadata" key="component" value="gts-root"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="CtsRootPermissionSignaturePermissionAllowlistNormalApp.apk" value="/data/local/tmp/cts-root-permission/CtsRootPermissionSignaturePermissionAllowlistNormalApp.apk" />
+        <option name="cleanup" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsRootPermissionTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.permission.cts_root" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="restart" value="false" />
+    </test>
+</configuration>
diff --git a/tests/permission/OWNERS b/tests/permission/OWNERS
new file mode 100644
index 0000000..fb6099c
--- /dev/null
+++ b/tests/permission/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/permission/apps/CtsRootPermissionSignaturePermissionAllowlistNormalApp/Android.bp b/tests/permission/apps/CtsRootPermissionSignaturePermissionAllowlistNormalApp/Android.bp
new file mode 100644
index 0000000..884d3f1
--- /dev/null
+++ b/tests/permission/apps/CtsRootPermissionSignaturePermissionAllowlistNormalApp/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 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: "CtsRootPermissionSignaturePermissionAllowlistNormalApp",
+    min_sdk_version: "30",
+    target_sdk_version: "35",
+}
diff --git a/tests/permission/apps/CtsRootPermissionSignaturePermissionAllowlistNormalApp/AndroidManifest.xml b/tests/permission/apps/CtsRootPermissionSignaturePermissionAllowlistNormalApp/AndroidManifest.xml
new file mode 100644
index 0000000..6a6ec99
--- /dev/null
+++ b/tests/permission/apps/CtsRootPermissionSignaturePermissionAllowlistNormalApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2024 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.permission.cts_root.apps.signaturepermissionallowlist.normal">
+
+    <uses-permission android:name="android.permission.BRICK" />
+    <uses-permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE" />
+
+    <application
+        android:hasCode="false"
+        android:label="CtsRootPermissionSignaturePermissionAllowlistNormalApp" />
+</manifest>
diff --git a/tests/permission/src/android/permission/cts-root/SignaturePermissionAllowlistTest.java b/tests/permission/src/android/permission/cts-root/SignaturePermissionAllowlistTest.java
new file mode 100644
index 0000000..10b3546
--- /dev/null
+++ b/tests/permission/src/android/permission/cts-root/SignaturePermissionAllowlistTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 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.permission.cts_root;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.SigningDetails;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.permission.PermissionManagerLocal;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.PackageState;
+import com.android.xts.root.annotations.RequireAdbRoot;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+@RequireAdbRoot
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+public final class SignaturePermissionAllowlistTest {
+    private static final String NORMAL_APP_APK_PATH = "/data/local/tmp/cts-root-permission/"
+            + "CtsRootPermissionSignaturePermissionAllowlistNormalApp.apk";
+    private static final String NORMAL_APP_PACKAGE_NAME =
+            "android.permission.cts_root.apps.signaturepermissionallowlist.normal";
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @NonNull
+    private final PackageManagerLocal mPackageManagerLocal =
+            LocalManagerRegistry.getManager(PackageManagerLocal.class);
+    @NonNull
+    private final PermissionManagerLocal mPermissionManagerLocal =
+            LocalManagerRegistry.getManager(PermissionManagerLocal.class);
+    @NonNull
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    @NonNull
+    private final PackageManager mPackageManager = mContext.getPackageManager();
+
+    @NonNull
+    private SigningDetails mNormalAppSigningDetails;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        assumeTrue(Build.isDebuggable());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mPermissionManagerLocal.setSignaturePermissionAllowlistForceEnforced(true);
+        installPackage(NORMAL_APP_APK_PATH);
+        SigningDetails platformSigningDetails;
+        try (var snapshot = mPackageManagerLocal.withUnfilteredSnapshot()) {
+            Map<String, PackageState> packageStates = snapshot.getPackageStates();
+            mNormalAppSigningDetails = packageStates.get(NORMAL_APP_PACKAGE_NAME)
+                    .getAndroidPackage().getSigningDetails();
+            platformSigningDetails = packageStates.get("android").getAndroidPackage()
+                    .getSigningDetails();
+        }
+        uninstallPackage(NORMAL_APP_PACKAGE_NAME);
+        mPackageManagerLocal.addOverrideSigningDetails(mNormalAppSigningDetails,
+                platformSigningDetails);
+        installPackage(NORMAL_APP_APK_PATH);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        uninstallPackage(NORMAL_APP_PACKAGE_NAME);
+        mPackageManagerLocal.removeOverrideSigningDetails(mNormalAppSigningDetails);
+        mPermissionManagerLocal.setSignaturePermissionAllowlistForceEnforced(false);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_SIGNATURE_PERMISSION_ALLOWLIST_ENABLED)
+    @Test
+    public void normalAppCanNotGetSignaturePermissionWithoutAllowlist() throws Exception {
+        assertThat(mPackageManager.checkPermission(android.Manifest.permission.BRICK,
+                NORMAL_APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_SIGNATURE_PERMISSION_ALLOWLIST_ENABLED)
+    @Test
+    public void normalAppCanGetSignaturePermissionWithAllowlist() throws Exception {
+        assertThat(mPackageManager.checkPermission(
+                android.Manifest.permission.RESERVED_FOR_TESTING_SIGNATURE,
+                NORMAL_APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void installPackage(@NonNull String apkPath) throws Exception {
+        SystemUtil.runShellCommandOrThrow("pm install " + apkPath);
+    }
+
+    private void uninstallPackage(@NonNull String packageName) throws Exception {
+        SystemUtil.runShellCommandOrThrow("pm uninstall " + packageName);
+    }
+}
diff --git a/tests/stats/Android.bp b/tests/stats/Android.bp
index 2b76c6a..2567075 100644
--- a/tests/stats/Android.bp
+++ b/tests/stats/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_telemetry_client_infra",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -34,8 +35,8 @@
         "platform-test-annotations",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.test",
+        "android.test.base.stubs.test",
     ],
     srcs: ["src/**/*.java"],
     test_suites: [
diff --git a/tests/usage/Android.bp b/tests/usage/Android.bp
index 5936bc4..7bd028b 100644
--- a/tests/usage/Android.bp
+++ b/tests/usage/Android.bp
@@ -28,8 +28,8 @@
         "truth",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.test",
+        "android.test.base.stubs.test",
         "services.core",
         "services.usage",
     ],