Merge "MediaRecorderTest: Increase the file size in MediaRecorderTest#testRecordExceedFileSizeLimit" into android13-tests-dev am: fd35825f90 am: 27a296003f am: 44f163b468

Original change: https://android-review.googlesource.com/c/platform/cts/+/2505836

Change-Id: Ia0affe13a882a73d28f1b081e98637c54f730908
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
index bf2b551..d76c7b9 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
@@ -20,17 +20,23 @@
 import android.util.Log;
 import androidx.annotation.Nullable;
 import com.android.compatibility.common.util.DeviceInfoStore;
+import com.google.common.base.Strings;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -40,6 +46,9 @@
 public final class LocaleDeviceInfo extends DeviceInfo {
     private static final String TAG = "LocaleDeviceInfo";
 
+    private static final Pattern HYPHEN_BINARY_PATTERN = Pattern.compile("hyph-(.*?).hyb");
+    private static final String HYPHEN_BINARY_LOCATION = "/system/usr/hyphen-data/";
+
     @Override
     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
         List<String> locales = Arrays.asList(
@@ -59,6 +68,26 @@
         }
         store.addListResult("icu_locale", icuLocales);
 
+        // Collect hyphenation supported locale
+        List<String> hyphenLocalesList = new ArrayList<>();
+        // Retrieve locale from the file name of binary
+        try (Stream<Path> stream = Files.walk(Paths.get(HYPHEN_BINARY_LOCATION))) {
+            hyphenLocalesList = stream
+                    .filter(file -> !Files.isDirectory(file))
+                    .map(Path::getFileName)
+                    .map(Path::toString)
+                    .filter(HYPHEN_BINARY_PATTERN.asPredicate())
+                    .map(s -> {
+                        Matcher matcher = HYPHEN_BINARY_PATTERN.matcher(s);
+                        return matcher.find() ? Strings.nullToEmpty(matcher.group(1)) : "";
+                    })
+                    .sorted()
+                    .collect(Collectors.toList());
+        } catch (IOException e) {
+            Log.w(TAG,"Hyphenation binary folder is not exist" , e);
+        }
+        store.addListResult("hyphenation_locale", hyphenLocalesList);
+
         collectLocaleDataFilesInfo(store);
     }
 
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index cd66d3e..2b16fc1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -26,6 +26,10 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import com.google.common.truth.Truth;
 
 import junit.framework.AssertionFailedError;
 
@@ -53,6 +57,10 @@
     private static final String CLASS = "com.android.cts.storageapp.StorageTest";
     private static final String CLASS_NO_APP_STORAGE =
             "com.android.cts.noappstorage.NoAppDataStorageTest";
+    private static final String EXTERNAL_STORAGE_PATH = "/storage/emulated/%d/";
+    private static final String ERROR_MESSAGE_TAG = "[ERROR]";
+
+    private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
 
     private int[] mUsers;
 
@@ -154,6 +162,12 @@
     }
 
     @Test
+    public void testVerifyStatsExternalForClonedUser() throws Exception {
+        int mCloneUserIdInt = createCloneUserAndInstallDeviceTestApk();
+        runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternal", mCloneUserIdInt, true);
+    }
+
+    @Test
     public void testVerifyStatsExternalConsistent() throws Exception {
         for (int user : mUsers) {
             runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternalConsistent", user, true);
@@ -293,4 +307,60 @@
             return false;
         }
     }
+
+    private int createCloneUserAndInstallDeviceTestApk() throws Exception {
+        // Create clone user.
+        String output = getDevice().executeShellCommand(
+                "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
+                        + "testUser");
+        String sCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]",
+                "");
+        Truth.assertThat(sCloneUserId).isNotEmpty();
+        // Start clone user.
+        CommandResult out = getDevice().executeShellV2Command("am start-user -w " + sCloneUserId);
+        Truth.assertThat(isSuccessful(out)).isTrue();
+
+        Integer mCloneUserIdInt = Integer.parseInt(sCloneUserId);
+        String sCloneUserStoragePath = String.format(EXTERNAL_STORAGE_PATH,
+                Integer.parseInt(sCloneUserId));
+        // Check that the clone user directories have been created
+        eventually(() -> getDevice().doesFileExist(sCloneUserStoragePath, mCloneUserIdInt),
+                CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+        // Install the DeviceTest APK for Clone User.
+        installPackage(APK_STATS, "--user all");
+        return mCloneUserIdInt;
+    }
+
+    private void eventually(ThrowingRunnable r, long timeoutMillis) {
+        long start = System.currentTimeMillis();
+
+        while (true) {
+            try {
+                r.run();
+                return;
+            } catch (Throwable e) {
+                if (System.currentTimeMillis() - start < timeoutMillis) {
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException ignored) {
+                        throw new RuntimeException(e);
+                    }
+                } else {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    private boolean isSuccessful(CommandResult result) {
+        if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+            return false;
+        }
+        String stdout = result.getStdout();
+        if (stdout.contains(ERROR_MESSAGE_TAG)) {
+            return false;
+        }
+        String stderr = result.getStderr();
+        return (stderr == null || stderr.trim().isEmpty());
+    }
 }
diff --git a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
index 74d59f7..452ffd6 100644
--- a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
@@ -117,6 +117,8 @@
     private static final String APPLY_DISABLE_DISPLAY_POWER_POLICY_CMD =
             "cmd car_service apply-power-policy cts_car_watchdog_disable_display";
 
+    private static final String FAILED_CUSTOM_COLLECTION_MSG = "Cannot start a custom collection";
+
     private static final long FIFTY_MEGABYTES = 1024 * 1024 * 50;
     private static final long TWO_HUNDRED_MEGABYTES = 1024 * 1024 * 200;
 
@@ -128,7 +130,8 @@
     private static final Pattern FOREGROUND_BYTES_PATTERN = Pattern.compile(
             "foregroundModeBytes = (\\d+)");
 
-    private static final long WATCHDOG_ACTION_TIMEOUT_MS = 15_000;
+    private static final long START_CUSTOM_COLLECTION_TIMEOUT_MS = 30_000;
+    private static final long WATCHDOG_ACTION_TIMEOUT_MS = 30_000;
 
     private boolean mDidModifyDateTime;
     private long mOriginalForegroundBytes;
@@ -153,7 +156,7 @@
                 GET_IO_OVERUSE_FOREGROUNG_BYTES_CMD));
         executeCommand("%s %d", SET_IO_OVERUSE_FOREGROUNG_BYTES_CMD, TWO_HUNDRED_MEGABYTES);
         executeCommand("logcat -c");
-        executeCommand(START_CUSTOM_PERF_COLLECTION_CMD);
+        startCustomCollection();
         executeCommand(RESET_RESOURCE_OVERUSE_CMD);
     }
 
@@ -380,4 +383,11 @@
         executeCommand("date %s", now.plusHours(1));
         CLog.d(TAG, "DateTime changed from %s to %s", now, now.plusHours(1));
     }
+
+    private void startCustomCollection() throws Exception {
+        PollingCheck.check("Could not start the custom collection.",
+                START_CUSTOM_COLLECTION_TIMEOUT_MS,
+                () -> !executeCommand(START_CUSTOM_PERF_COLLECTION_CMD)
+                        .contains(FAILED_CUSTOM_COLLECTION_MSG));
+    }
 }
diff --git a/hostsidetests/car_builtin/OWNERS b/hostsidetests/car_builtin/OWNERS
index f0afac5..c5bee2d 100644
--- a/hostsidetests/car_builtin/OWNERS
+++ b/hostsidetests/car_builtin/OWNERS
@@ -1,4 +1,2 @@
 # Bug component: 526680
-keunyoung@google.com
-felipeal@google.com
-gurunagarajan@google.com
+include platform/packages/services/Car:/OWNERS
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 518144a..46fd3dc 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -489,6 +489,7 @@
         assertInteger(parts[8]); // attention
         assertInteger(parts[9]); // faceDown
         assertInteger(parts[10]); // deviceState
+
     }
 
     private void checkUserActivityCompat(String[] parts) {
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index cd9378d..72cd866 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -20,6 +20,8 @@
 
 import android.platform.test.annotations.AppModeFull;
 
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
@@ -147,24 +149,36 @@
     @Test
     public void testCheckInstallerAppAccessToObbDirs() throws Exception {
         allowAppOps("android:request_install_packages");
-        grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+        if (isSdkLevelLessThanT()) {
+            grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        }
         try {
             runDeviceTest("testCheckInstallerAppAccessToObbDirs");
         } finally {
             denyAppOps("android:request_install_packages");
-            revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+            // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+            if (isSdkLevelLessThanT()) {
+                revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+            }
         }
     }
 
     @Test
     public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
         allowAppOps("android:request_install_packages");
-        grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+        if (isSdkLevelLessThanT()) {
+            grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        }
         try {
             runDeviceTest("testCheckInstallerAppCannotAccessDataDirs");
         } finally {
             denyAppOps("android:request_install_packages");
-            revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+            // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+            if (isSdkLevelLessThanT()) {
+                revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+            }
         }
     }
 
@@ -389,4 +403,9 @@
             executeShellCommand("cmd appops set --uid android.scopedstorage.cts " + op + " deny");
         }
     }
+
+    private boolean isSdkLevelLessThanT() throws DeviceNotAvailableException {
+        DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(getDevice());
+        return !deviceSdkLevel.isDeviceAtLeastT();
+    }
 }
diff --git a/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java b/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java
index a72fe96..0fdc3ea 100644
--- a/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java
+++ b/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java
@@ -1,9 +1,5 @@
 package android.security.cts;
 
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
@@ -74,19 +70,28 @@
         //
         // This test asserts that, if present, /dev/hw_random must:
         //
-        // 1. Have ownership root:root or prng_seeder:prng_seeder
-        // 2. Have permissions 0600 (the kernel default) or 0400 (prng seeder default)
+        // 1. Have ownership prng_seeder:prng_seeder
+        // 2. Have permissions 0400 - The only user space process requiring
+        //    access is the PRNG seeder daemon which only needs read access.
         // 3. Be a character device with major:minor 10:183 (the kernel
         //    default), which in the hex output by stat corresponds to a:b7
         //
         // That translates to "stat -c %t,%T:%a:%U:%G: output that corresponds to one
         // of the options below.
 
-        String command = "stat -c %t,%T:%a:%U:%G " + HW_RNG_DEVICE;
-        String output = mDevice.executeShellCommand(command).trim();
-        assertThat("WRONG major, minor, mode or ownership on " + HW_RNG_DEVICE, output,
-                anyOf(is("a,b7:600:root:root"),
-                      is("a,b7:400:prng_seeder:prng_seeder")));
+        // That translates to `ls -l` output like this:
+        // cr-------- 1 prng_seeder prng_seeder 10, 183 2021-02-11 17:55 /dev/hw_random
 
+        String command = "ls -l " + HW_RNG_DEVICE;
+        String output = mDevice.executeShellCommand(command).trim();
+        if (!output.endsWith(" " + HW_RNG_DEVICE)) {
+            fail("Unexpected output from " + command + ": \"" + output + "\"");
+        }
+        String[] outputWords = output.split("\\s");
+        assertEquals("Wrong mode on " + HW_RNG_DEVICE, "cr--------", outputWords[0]);
+        assertEquals("Wrong owner of " + HW_RNG_DEVICE, "prng_seeder", outputWords[2]);
+        assertEquals("Wrong group of " + HW_RNG_DEVICE, "prng_seeder", outputWords[3]);
+        assertEquals("Wrong device major on " + HW_RNG_DEVICE, "10,", outputWords[4]);
+        assertEquals("Wrong device minor on " + HW_RNG_DEVICE, "183", outputWords[5]);
     }
 }
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index 9585493..063d3f8 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -42,6 +42,7 @@
         "src/**/net/*.java",
         "src/**/notification/*.java",
         "src/**/perfetto/*.java",
+        "src/**/performancehintmanager/*.java",
         "src/**/permissionstate/*.java",
         "src/**/settingsstats/*.java",
         "src/**/sizecompatrestartbutton/*.java",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index f4b26cc..55f0aaf 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -20,6 +20,8 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertNotNull;
+
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.ActivityManager;
@@ -62,6 +64,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.PerformanceHintManager;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -1164,4 +1167,18 @@
         GameManager gameManager = context.getSystemService(GameManager.class);
         gameManager.setGameState(new GameState(true, GameState.MODE_CONTENT, 1, 2));
     }
+
+    @Test
+    public void testCreateHintSession() throws Exception {
+        final long targetNs = 16666666L;
+        Context context = InstrumentationRegistry.getContext();
+        PerformanceHintManager phm = context.getSystemService(PerformanceHintManager.class);
+
+        assertNotNull(phm);
+
+        PerformanceHintManager.Session session =
+                phm.createHintSession(new int[]{Process.myPid()}, targetNs);
+
+        assertNotNull(session);
+    }
 }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java
index fcd1048..86d8bcb 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java
@@ -229,9 +229,22 @@
         List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertThat(data.size()).isAtLeast(1);
         boolean found = false;
+        final int expectedUid = getUid(ALARM_ATOM_TEST_PACKAGE);
         for (int i = 0; i < data.size(); i++) {
             AtomsProto.AlarmBatchDelivered abd = data.get(i).getAtom().getAlarmBatchDelivered();
-            found |= abd.getWakeups() >= 1 && abd.getNumAlarms() >= 1;
+            int expectedNumAlarms = 0;
+            int expectedWakeups = 0;
+            for (int j = 0; j < abd.getUidsCount(); j++) {
+                expectedNumAlarms += abd.getNumAlarmsPerUid(j);
+                expectedWakeups += abd.getNumWakeupsPerUid(j);
+                if (abd.getUids(j) == expectedUid) {
+                    assertThat(abd.getNumAlarmsPerUid(j)).isEqualTo(1);
+                    assertThat(abd.getNumWakeupsPerUid(j)).isEqualTo(1);
+                    found = true;
+                }
+            }
+            assertThat(abd.getNumAlarms()).isEqualTo(expectedNumAlarms);
+            assertThat(abd.getWakeups()).isEqualTo(expectedWakeups);
         }
         assertThat(found).isTrue();
     }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
index 081e08f..6701860 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
@@ -23,6 +23,7 @@
 import android.cts.statsdatom.lib.DeviceUtils;
 import android.cts.statsdatom.lib.ReportUtils;
 
+import com.android.annotations.Nullable;
 import com.android.os.AtomsProto;
 import com.android.os.StatsLog;
 import com.android.tradefed.build.IBuildInfo;
@@ -76,12 +77,46 @@
     }
 
     public void testAppStartOccurred() throws Exception {
+        runTestAppStartOccurredCommon(null, atom -> {
+            assertThat(atom.getType()).isEqualTo(
+                    AtomsProto.AppStartOccurred.TransitionType.COLD);
+            assertThat(atom.getProcessState()).isEqualTo(
+                    AtomsProto.AppStartOccurred.AppProcessState.PROCESS_STATE_NONEXISTENT);
+        });
+    }
+
+    public void testAppStartOccurredWarm() throws Exception {
+        runTestAppStartOccurredCommon(() -> {
+            DeviceUtils.executeBackgroundService(getDevice(), "action.end_immediately");
+            Thread.sleep(WAIT_TIME_MS);
+        }, atom -> {
+            assertThat(atom.getType()).isEqualTo(
+                    AtomsProto.AppStartOccurred.TransitionType.WARM);
+            assertThat(atom.getProcessState()).isEqualTo(
+                    AtomsProto.AppStartOccurred.AppProcessState.PROCESS_STATE_CACHED_EMPTY);
+        });
+    }
+
+    private interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    private interface ConsumerWithException<T> {
+        void accept(T t) throws Exception;
+    }
+
+    private void runTestAppStartOccurredCommon(@Nullable RunnableWithException prolog,
+            @Nullable ConsumerWithException<AtomsProto.AppStartOccurred> epilog) throws Exception {
         final int atomTag = AtomsProto.Atom.APP_START_OCCURRED_FIELD_NUMBER;
         ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
                 atomTag,  /*uidInAttributionChain=*/false);
         getDevice().executeShellCommand(getGlobalHibernationCommand(
                 DeviceUtils.STATSD_ATOM_TEST_PKG, false));
 
+        if (prolog != null) {
+            prolog.run();
+        }
+
         DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
                 STATSD_CTS_FOREGROUND_ACTIVITY, "action", "action.sleep_top", WAIT_TIME_MS);
 
@@ -97,6 +132,9 @@
         assertThat(atom.getActivityStartTimestampMillis()).isGreaterThan(0L);
         assertThat(atom.getTransitionDelayMillis()).isGreaterThan(0);
         assertThat(atom.getIsHibernating()).isFalse();
+        if (epilog != null) {
+            epilog.accept(atom);
+        }
     }
 
     public void testHibernatingAppStartOccurred() throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
index be665ba..7f29d44 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
@@ -69,10 +69,6 @@
                 .addAllowedLogSource("AID_SYSTEM")
                 .addAllowedLogSource("AID_BLUETOOTH")
                 .addAllowedLogSource("com.android.bluetooth")
-                // TODO(b/236681553): Remove this.
-                .addAllowedLogSource("com.android.bluetooth.services")
-                // TODO(b/236681553): Remove this.
-                .addAllowedLogSource("com.google.android.bluetooth.services")
                 .addAllowedLogSource("AID_LMKD")
                 .addAllowedLogSource("AID_MEDIA")
                 .addAllowedLogSource("AID_RADIO")
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/OWNERS
new file mode 100644
index 0000000..4b34064
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/OWNERS
@@ -0,0 +1,5 @@
+# Owners of ADPF atoms and the CTS test
+chingtangyu@google.com
+lpy@google.com
+mattbuckley@google.com
+xwxw@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/PerformanceHintManagerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/PerformanceHintManagerStatsTests.java
new file mode 100644
index 0000000..fe1378d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/PerformanceHintManagerStatsTests.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.performancehintmanager;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+/**
+ * Test for Performance Hint Manager stats.
+ * This test is mainly to test ADPF data collection
+ *
+ *  <p>Build/Install/Run:
+ *  atest CtsStatsdAtomHostTestCases:PerformanceHintManagerStatsTests
+ */
+public class PerformanceHintManagerStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testCreateHintSessionStatsd() throws Exception {
+        if (Boolean.parseBoolean(
+                DeviceUtils.getProperty(getDevice(), "debug.hwui.use_hint_manager"))) {
+            ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                    AtomsProto.Atom.PERFORMANCE_HINT_SESSION_REPORTED_FIELD_NUMBER);
+            DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(),
+                    ".AtomTests", "testCreateHintSession");
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+            List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+            assertThat(data.size()).isAtLeast(1);
+            AtomsProto.PerformanceHintSessionReported a0 =
+                    data.get(0).getAtom().getPerformanceHintSessionReported();
+            assertThat(a0.getPackageUid()).isGreaterThan(10000);  // Not a system service UID.
+            assertThat(a0.getSessionId()).isNotEqualTo(0);
+            assertThat(a0.getTargetDurationNs()).isEqualTo(16666666L);
+            assertThat(a0.getTidCount()).isEqualTo(1);
+        }
+    }
+
+    public void testAdpfSystemComponentStatsd() throws Exception {
+        final boolean isSurfaceFlingerCpuHintEnabled = Boolean.parseBoolean(
+                DeviceUtils.getProperty(getDevice(), "debug.sf.enable_adpf_cpu_hint"));
+        final boolean isHwuiHintEnabled = Boolean.parseBoolean(
+                DeviceUtils.getProperty(getDevice(), "debug.hwui.use_hint_manager"));
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.ADPF_SYSTEM_COMPONENT_INFO_FIELD_NUMBER);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(data.size()).isAtLeast(1);
+        AtomsProto.ADPFSystemComponentInfo a0 = data.get(0).getAdpfSystemComponentInfo();
+        assertThat(a0.getSurfaceflingerCpuHintEnabled()).isEqualTo(isSurfaceFlingerCpuHintEnabled);
+        assertThat(a0.getHwuiHintEnabled()).isEqualTo(isHwuiHintEnabled);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 035b679..ef1d686 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -27,8 +27,6 @@
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
 import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
@@ -59,12 +57,10 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
-import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
 import android.accessibilityservice.cts.activities.NonDefaultDisplayActivity;
 import android.app.Activity;
-import android.app.ActivityTaskManager;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.graphics.Rect;
@@ -88,7 +84,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.TestUtils;
 
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index d79f909..540303b 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -22,6 +22,7 @@
 
 import android.app.Notification;
 import android.app.Notification.Action.Builder;
+import android.app.Notification.CallStyle;
 import android.app.Notification.MessagingStyle;
 import android.app.Notification.MessagingStyle.Message;
 import android.app.NotificationChannel;
@@ -981,6 +982,20 @@
         assertFalse(mNotification.hasImage());
     }
 
+    public void testCallStyle_setsChronometerExtra() {
+        Person person = new Person.Builder().setName("Test name").build();
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        CallStyle cs = CallStyle.forIncomingCall(person, pendingIntent, pendingIntent);
+        Notification.Builder builder = new Notification.Builder(mContext, CHANNEL.getId())
+                .setStyle(cs)
+                .setUsesChronometer(true);
+
+        Notification notification = builder.build();
+        Bundle extras = notification.extras;
+        assertTrue(extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER));
+    }
+
     private static void assertMessageEquals(
             Notification.MessagingStyle.Message expected,
             Notification.MessagingStyle.Message actual) {
diff --git a/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt b/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt
index f4692c7..1b03bd0 100644
--- a/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt
+++ b/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt
@@ -92,7 +92,7 @@
     }
 
     @Test
-    @Ignore("b/236749332")
+    @Ignore("b/252781795")
     fun closeToSender_displaysChip() {
         statusBarManager.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
@@ -108,7 +108,7 @@
     }
 
     @Test
-    @Ignore("236749332")
+    @Ignore("b/252781795")
     fun farFromSender_hidesChip() {
         // First, make sure we display the chip
         statusBarManager.updateMediaTapToTransferReceiverDisplay(
diff --git a/tests/attentionservice/Android.bp b/tests/attentionservice/Android.bp
index a515cf7..84c765a 100644
--- a/tests/attentionservice/Android.bp
+++ b/tests/attentionservice/Android.bp
@@ -16,6 +16,17 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_library {
+    name: "CtsAttentionServiceDevice",
+    srcs: ["src/**/CtsTestAttentionService.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+    ],
+}
+
 android_test {
     name: "CtsAttentionServiceDeviceTestCases",
     defaults: ["cts_defaults"],
@@ -34,5 +45,5 @@
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
     test_suites: ["cts"],
-//    sdk_version: "system_current",
+    // sdk_version: "system_current",
 }
diff --git a/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java b/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java
index 01adafd..a16cbcd 100644
--- a/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java
+++ b/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java
@@ -37,7 +37,7 @@
     @Override
     public void onCancelAttentionCheck(AttentionCallback callback) {
         callback.onFailure(ATTENTION_FAILURE_CANCELLED);
-        reset();
+        resetAttentionCheck();
         sRespondLatch.countDown();
     }
 
@@ -49,12 +49,20 @@
 
     @Override
     public void onStopProximityUpdates() {
-        reset();
+        resetProximity();
         sRespondLatch.countDown();
     }
 
     public static void reset() {
+        resetAttentionCheck();
+        resetProximity();
+    }
+
+    public static void resetAttentionCheck() {
         sCurrentAttentionCallback = null;
+    }
+
+    public static void resetProximity() {
         sCurrentProximityUpdateCallback = null;
     }
 
@@ -62,14 +70,14 @@
         if (sCurrentAttentionCallback != null) {
             sCurrentAttentionCallback.onSuccess(code, 0);
         }
-        reset();
+        resetAttentionCheck();
     }
 
     public static void respondFailure(int code) {
         if (sCurrentAttentionCallback != null) {
             sCurrentAttentionCallback.onFailure(code);
         }
-        reset();
+        resetAttentionCheck();
     }
 
     public static void respondProximity(double distance) {
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index bc84a98..a69e252 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -154,6 +154,8 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".activities.FadeInActivity"/>
+        <activity android:name=".activities.MultipleStepsSignInActivity"/>
         <activity android:name=".activities.FieldsNoPasswordActivity"/>
         <activity android:name=".activities.AugmentedAuthActivity" />
         <activity android:name=".activities.SimpleAfterLoginActivity"/>
diff --git a/tests/autofillservice/res/layout/fade_in_activity.xml b/tests/autofillservice/res/layout/fade_in_activity.xml
new file mode 100644
index 0000000..3efc4d6
--- /dev/null
+++ b/tests/autofillservice/res/layout/fade_in_activity.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  * Copyright (C) 2022 The Android Open Source Project
+  *
+  * Licensed under the Apache License, Version 2.0 (the "License");
+  * you may not use this file except in compliance with the License.
+  * You may obtain a copy of the License at
+  *
+  *      http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/password_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Password" />
+
+        <EditText
+            android:id="@+id/password"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textPassword"
+            android:textCursorDrawable="@android:color/transparent"
+            android:imeOptions="flagNoFullscreen" />
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/multiple_steps_activity.xml b/tests/autofillservice/res/layout/multiple_steps_activity.xml
new file mode 100644
index 0000000..df00cfa
--- /dev/null
+++ b/tests/autofillservice/res/layout/multiple_steps_activity.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="200dp"
+        android:orientation="horizontal">
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/prev"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="PREV" />
+
+        <Button
+            android:id="@+id/next"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="NEXT" />
+
+        <Button
+            android:id="@+id/finish"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="FINISH" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/autofillservice/res/layout/password.xml b/tests/autofillservice/res/layout/password.xml
new file mode 100644
index 0000000..39d60d6
--- /dev/null
+++ b/tests/autofillservice/res/layout/password.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/password_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Password" />
+
+    <EditText
+        android:id="@+id/password"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="textPassword"
+        android:textCursorDrawable="@android:color/transparent"
+        android:imeOptions="flagNoFullscreen" />
+
+</LinearLayout>
diff --git a/tests/autofillservice/res/layout/username.xml b/tests/autofillservice/res/layout/username.xml
new file mode 100644
index 0000000..a77a1d3
--- /dev/null
+++ b/tests/autofillservice/res/layout/username.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/username_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Username" />
+
+    <EditText
+        android:id="@+id/username"
+        android:minEms="2"
+        android:maxEms="5"
+        android:maxLength="25"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textCursorDrawable="@android:color/transparent"
+        android:imeOptions="flagNoFullscreen" />
+
+</LinearLayout>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
index 70e04b7..af30990 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.view.PixelCopy;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.autofill.AutofillManager;
 
 import androidx.annotation.NonNull;
@@ -183,4 +184,11 @@
     public void clearFocus() {
         throw new UnsupportedOperationException("Not implemented by " + getClass());
     }
+
+    /**
+     * Get insets of the root window
+     */
+    public WindowInsets getRootWindowInsets() {
+        return getWindow().getDecorView().getRootWindowInsets();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FadeInActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FadeInActivity.java
new file mode 100644
index 0000000..c4a1911
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FadeInActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+
+/**
+ * Activity that do fade-in animation at beginning:
+ */
+public class FadeInActivity extends AbstractAutoFillActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fade_in_activity);
+
+        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
index fae0587..f05529f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
@@ -28,7 +28,6 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.view.WindowInsets;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
@@ -398,13 +397,6 @@
     }
 
     /**
-     * Get insets of the root window
-     */
-    public WindowInsets getRootWindowInsets() {
-        return mUsernameLabel.getRootWindowInsets();
-    }
-
-    /**
      * Request to hide soft input
      */
     public void hideSoftInput() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultipleStepsSignInActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultipleStepsSignInActivity.java
new file mode 100644
index 0000000..3bf4f87
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultipleStepsSignInActivity.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.autofill.AutofillManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MultipleStepsSignInActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "AbstractMultipleStepsActivity";
+    private static final String MESSAGE_STEP = "Showing step ";
+    private static final String MESSAGE_FINISH = "Finished";
+
+    private static MultipleStepsSignInActivity sCurrentActivity;
+
+    /**
+     * Gests the latest instance.
+     *
+     * <p>Typically used in test cases that rotates the activity
+     */
+    @SuppressWarnings("unchecked") // Its up to caller to make sure it's setting the right one
+    public static <T extends MultipleStepsSignInActivity> T getCurrentActivity() {
+        return (T) sCurrentActivity;
+    }
+
+    private TextView mStatus;
+    private ViewGroup mContainer;
+
+    private Button mPrevButton;
+    private Button mNextButton;
+    private Button mFinishButton;
+
+    private int mCurrentStep;
+    private boolean mFinished;
+
+    protected List<LinearLayout> mSteps;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        sCurrentActivity = this;
+        mCurrentStep = 0;
+
+        setContentView(R.layout.multiple_steps_activity);
+
+        mStatus = findViewById(R.id.status);
+        mContainer = findViewById(R.id.container);
+        mPrevButton = findViewById(R.id.prev);
+        mNextButton = findViewById(R.id.next);
+        mFinishButton = findViewById(R.id.finish);
+
+        View.OnClickListener onClickListener = (v) -> {
+            if (v == mPrevButton) {
+                showStep(mCurrentStep - 1);
+            } else if (v == mNextButton) {
+                showStep(mCurrentStep + 1);
+            } else {
+                finishSelf();
+            }
+        };
+        mPrevButton.setOnClickListener(onClickListener);
+        mNextButton.setOnClickListener(onClickListener);
+        mFinishButton.setOnClickListener(onClickListener);
+
+        mSteps = getStepsMap();
+
+        showStep(0);
+    }
+
+    public void nextPage() {
+        runOnUiThread(() -> mNextButton.performClick());
+    }
+
+    public void prevPage() {
+        runOnUiThread(() -> mPrevButton.performClick());
+    }
+
+    public void hideSoftInput() {
+        final InputMethodManager imm = getSystemService(InputMethodManager.class);
+        imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
+    }
+
+    private void showStep(int i) {
+        if (mFinished || i < 0 || i >= mSteps.size()) {
+            Log.w(TAG, String.format("Invalid step: %d (finished=%b, range=[%d,%d])",
+                    i, mFinished, 0, mSteps.size() - 1));
+            return;
+        }
+
+        View step = mSteps.get(i);
+        mStatus.setText(MESSAGE_STEP + i);
+        Log.d(TAG, "Showing step " + i);
+
+        if (mContainer.getChildCount() > 0) {
+            mContainer.removeViewAt(0);
+        }
+        mContainer.addView(step);
+        mCurrentStep = i;
+
+        mPrevButton.setEnabled(mCurrentStep != 0);
+        mNextButton.setEnabled(mCurrentStep != mSteps.size() - 1);
+    }
+
+    private void finishSelf() {
+        mStatus.setText(MESSAGE_FINISH);
+        mContainer.removeAllViews();
+        mFinished = true;
+        AutofillManager afm = getSystemService(AutofillManager.class);
+        if (afm != null) {
+            afm.commit();
+        }
+    }
+
+    protected List<LinearLayout> getStepsMap() {
+        List<LinearLayout> steps = new ArrayList<>(2);
+        steps.add(newStep(R.layout.username));
+        steps.add(newStep(R.layout.password));
+        return ImmutableList.copyOf(steps);
+    }
+
+    private LinearLayout newStep(int resId) {
+        final LayoutInflater inflater = LayoutInflater.from(this);
+        return (LinearLayout) inflater.inflate(resId, null);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
index 9ab86ce..8ca37c4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
@@ -135,7 +135,7 @@
         assertFillEventForDatasetShown(events.get(0), "clientStateKey",
                 "clientStateValue", presentationType);
         assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
-                "clientStateKey", "clientStateValue");
+                "clientStateKey", "clientStateValue", presentationType);
     }
 
     @Test
@@ -180,7 +180,7 @@
         assertFillEventForDatasetShown(events.get(0), "clientStateKey",
                 "clientStateValue", presentationType);
         assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
-                "clientStateKey", "clientStateValue");
+                "clientStateKey", "clientStateValue", presentationType);
         assertFillEventForDatasetShown(events.get(2), "clientStateKey",
                 "clientStateValue", presentationType);
         assertFillEventForDatasetSelected(events.get(3), "name",
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dialog/FadeInActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dialog/FadeInActivityTest.java
new file mode 100644
index 0000000..df62027
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dialog/FadeInActivityTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dialog;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.enableFillDialogFeature;
+import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
+
+import android.autofillservice.cts.activities.FadeInActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.content.Intent;
+
+import org.junit.Test;
+
+
+/**
+ * This is the test cases about fade-in animation for the fill dialog UI.
+ */
+public class FadeInActivityTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    @Test
+    public void testShowFillDialog_withFadeInAnimation() throws Exception {
+        // Enable feature and test service
+        enableFillDialogFeature(sContext);
+        enableService();
+
+        // Set response
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Dropdown Presentation"))
+                        .setDialogPresentation(createPresentation("Dialog Presentation"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_PASSWORD);
+        sReplier.addResponse(builder.build());
+
+        // Start activity
+        startFadeInActivity();
+
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Click on password field to trigger fill dialog
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        // Set expected value, then select dataset
+        mUiBot.assertFillDialogDatasets("Dialog Presentation");
+    }
+
+    private void startFadeInActivity() throws Exception {
+        final Intent intent = new Intent(mContext, FadeInActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiBot.assertShownByRelativeId(Helper.ID_PASSWORD_LABEL);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dialog/MultipleStepsSignInActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dialog/MultipleStepsSignInActivityTest.java
new file mode 100644
index 0000000..cd942b6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dialog/MultipleStepsSignInActivityTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dialog;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertMockImeStatus;
+import static android.autofillservice.cts.testcore.Helper.enableFillDialogFeature;
+import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
+
+import android.autofillservice.cts.activities.MultipleStepsSignInActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.content.Intent;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Test;
+
+
+/**
+ * The tests for showing fill dialog for an Activity that only updates the content for login
+ * steps, the app doesn't go to the new activty.
+ */
+public class MultipleStepsSignInActivityTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+    MultipleStepsSignInActivity mActivity;
+    @After
+    public void teardown() {
+        if (mActivity != null) {
+            mActivity.finish();
+        }
+        mActivity = null;
+    }
+    @Test
+    public void testShowFillDialog_contentChanged_shownFillDialog() throws Exception {
+        // Enable feature and test service
+        enableFillDialogFeature(sContext);
+        enableService();
+
+        // Set response
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("Menu Username"))
+                        .setDialogPresentation(createPresentation("Dialog Username"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_USERNAME);
+        sReplier.addResponse(builder.build());
+
+        // Start activity
+        mActivity = startMultipleStepsSignInActivity();
+
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Click on username field to trigger fill dialog
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Verify fill dialog shown
+        mUiBot.assertFillDialogDatasets("Dialog Username");
+
+        // Do nothing for fill dialog. Click outside to hide fill dialog and IME
+        hideFillDialogAndIme(mActivity);
+
+        // Set response for second page
+        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Menu Password"))
+                        .setDialogPresentation(createPresentation("Dialog Password"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_PASSWORD);
+        sReplier.addResponse(builder2.build());
+
+        mActivity.nextPage();
+
+        mUiBot.assertShownByRelativeId(ID_PASSWORD_LABEL);
+
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Click on password field to trigger fill dialog
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        // Verify fill dialog shown
+        mUiBot.assertFillDialogDatasets("Dialog Password");
+    }
+
+    @Test
+    public void testShowFillDialog_backPrevPage_notShownFillDialog() throws Exception {
+        // Enable feature and test service
+        enableFillDialogFeature(sContext);
+        enableService();
+
+        // Set response
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("Menu Username"))
+                        .setDialogPresentation(createPresentation("Dialog Username"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_USERNAME);
+        sReplier.addResponse(builder.build());
+
+        // Start activity
+        mActivity = startMultipleStepsSignInActivity();
+
+        Log.e("tymtest", "autofill etst 1");
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+        Log.e("tymtest", "autofill etst 2");
+        // Set response for second page
+        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Menu Password"))
+                        .setDialogPresentation(createPresentation("Dialog Password"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_PASSWORD);
+        sReplier.addResponse(builder2.build());
+
+        // Do nothing on the 1st page and go to next page
+        mActivity.nextPage();
+
+        mUiBot.assertShownByRelativeId(ID_PASSWORD_LABEL);
+
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Click on password field to trigger fill dialog
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        // Verify fill dialog shown
+        mUiBot.assertFillDialogDatasets("Dialog Password");
+
+        // Go back previous page
+        mActivity.prevPage();
+
+        mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+
+        // Verify there is no any fill request because response already exists
+        sReplier.assertNoUnhandledFillRequests();
+
+        // Click on username field to trigger menu UI
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Verify fill menu shown
+        mUiBot.assertDatasets("Menu Username");
+    }
+
+    @Test
+    public void testShowFillDialog_doNothingThenBackPrevPage_notShownFillDialog() throws Exception {
+        // Enable feature and test service
+        enableFillDialogFeature(sContext);
+        enableService();
+
+        // Set response
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("Menu Username"))
+                        .setDialogPresentation(createPresentation("Dialog Username"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_USERNAME);
+        sReplier.addResponse(builder.build());
+
+        // Start activity
+        mActivity = startMultipleStepsSignInActivity();
+
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Set response for second page
+        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Menu Password"))
+                        .setDialogPresentation(createPresentation("Dialog Password"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_PASSWORD);
+        sReplier.addResponse(builder2.build());
+
+        // Do nothing on the 1st page and go to next page
+        mActivity.nextPage();
+
+        mUiBot.assertShownByRelativeId(ID_PASSWORD_LABEL);
+
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Do nothing and go back previous page
+        mActivity.prevPage();
+
+        mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+
+        // Verify there is no any fill request because response already exists
+        sReplier.assertNoUnhandledFillRequests();
+
+        // Click on username field to trigger menu UI
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Verify fill menu shown
+        mUiBot.assertDatasets("Menu Username");
+    }
+
+    private void hideFillDialogAndIme(MultipleStepsSignInActivity activity) throws Exception {
+        // Hide fill dialog via touch outside, the ime will appear.
+        mUiBot.touchOutsideDialog();
+        mUiBot.waitForIdleSync();
+
+        assertMockImeStatus(activity, /* expectedImeShow= */ true);
+
+        // Hide the IME before the next test.
+        activity.hideSoftInput();
+
+        assertMockImeStatus(activity, /* expectedImeShow= */ false);
+    }
+
+    private MultipleStepsSignInActivity startMultipleStepsSignInActivity() throws Exception {
+        final Intent intent = new Intent(mContext, MultipleStepsSignInActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+        mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+        return MultipleStepsSignInActivity.getCurrentActivity();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index 3a40ca9..fc34db6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -39,7 +39,7 @@
 import android.app.assist.AssistStructure.ViewNode;
 import android.app.assist.AssistStructure.WindowNode;
 import android.autofillservice.cts.R;
-import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
 import android.content.AutofillOptions;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -1254,12 +1254,12 @@
      * @param event event to be asserted
      * @param key the only key expected in the client state bundle
      * @param value the only value expected in the client state bundle
-     * @param expectedPresentation the exptected ui presentation type
+     * @param uiType the expected ui presentation type
      */
     public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
-            @NonNull String key, @NonNull String value, int expectedPresentation) {
+            @NonNull String key, @NonNull String value, int uiType) {
         assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
-        assertFillEventPresentationType(event, expectedPresentation);
+        assertFillEventPresentationType(event, uiType);
     }
 
     /**
@@ -1269,9 +1269,9 @@
      * @param event event to be asserted
      */
     public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
-            int expectedPresentation) {
+            int uiType) {
         assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
-        assertFillEventPresentationType(event, expectedPresentation);
+        assertFillEventPresentationType(event, uiType);
     }
 
     /**
@@ -1283,11 +1283,13 @@
      * @param datasetId dataset set id expected in the event
      * @param key the only key expected in the client state bundle
      * @param value the only value expected in the client state bundle
+     * @param uiType the expected ui presentation type
      */
     public static void assertFillEventForDatasetAuthenticationSelected(
             @NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+            @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) {
         assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+        assertFillEventPresentationType(event, uiType);
     }
 
     /**
@@ -1298,11 +1300,13 @@
      * @param datasetId dataset set id expected in the event
      * @param key the only key expected in the client state bundle
      * @param value the only value expected in the client state bundle
+     * @param uiType the expected ui presentation type
      */
     public static void assertFillEventForAuthenticationSelected(
             @NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+            @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) {
         assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+        assertFillEventPresentationType(event, uiType);
     }
 
     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
@@ -1701,7 +1705,7 @@
     /**
      * Asserts whether mock IME is showing
      */
-    public static void assertMockImeStatus(LoginActivity activity,
+    public static void assertMockImeStatus(AbstractAutoFillActivity activity,
             boolean expectedImeShow) throws Exception {
         Timeouts.MOCK_IME_TIMEOUT.run("assertMockImeStatus(" + expectedImeShow + ")",
                 () -> {
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index 90b3bf3..2c88064 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -27,6 +27,7 @@
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.StateCallback;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
 import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
@@ -166,15 +167,6 @@
                 ids.length == 0 ||
                 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY));
 
-        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            // Camera placement on automotive device is different than usual front/back
-            // on mobile phones and use automotive.lens.facing instead. lens.facing is only used for 
-            // external camera.testCameraManagerAutomotiveCameras ensures that lens.facing is only
-            // used for EXTERNAL camera. Hence, skipping this test for automotive implementations
-            Log.i(TAG, "Skip rest of the test on automotive device implementations");
-            return;
-        }
-
         /**
          * Test: that if the device has front or rear facing cameras, then there
          * must be matched system features.
@@ -1051,23 +1043,15 @@
          *   android.automotive.lens.facing values
          */
         Map<Pair<Integer, Integer>, ArrayList<String>> cameraGroup = new HashMap<>();
-        boolean externalCameraConnected = false;
         for (String cameraId : cameraIds) {
             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
             assertNotNull(
                     String.format("Can't get camera characteristics from: ID %s", cameraId), props);
 
             Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING);
-
-            if (lensFacing != null) {
-                // Automotive device implementations can use android.lens.facing
-                // only for external cameras
-                assertTrue("android.lens.facing should only be used for external cameras",
-                        lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL);
-                // Test that there is matching feature flag
-                assertTrue("System doesn't have external camera feature",
-                        mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL));
-                externalCameraConnected = true;
+            if (lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) {
+                // Automotive device implementations may have external cameras but they are exempted
+                // from this test case.
                 continue;
             }
 
@@ -1098,13 +1082,6 @@
             }
         }
 
-        // Test an external camera is connected if FEATURE_CAMERA_EXTERNAL is advertised
-        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)) {
-            assertTrue("External camera is not connected on device with FEATURE_CAMERA_EXTERNAL",
-                     externalCameraConnected);
-        }
-
-
         for (Map.Entry<Pair<Integer, Integer>, ArrayList<String>> entry : cameraGroup.entrySet()) {
             ArrayList<String> cameraIdsToVerify = entry.getValue();
             if (cameraIdsToVerify.size() > 1) {
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
index 5ffe5fa..caa9d2e 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
@@ -18,6 +18,10 @@
 
 import static android.Manifest.permission.READ_NEARBY_STREAMING_POLICY;
 
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.assertThrows;
@@ -28,17 +32,21 @@
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
 import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
 import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
 import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
 import com.android.bedstead.harrier.annotations.enterprise.PolicyAppliesTest;
 import com.android.bedstead.harrier.annotations.enterprise.PolicyDoesNotApplyTest;
 import com.android.bedstead.harrier.policies.NearbyAppStreamingPolicy;
 import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
 
-import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(BedsteadJUnit4.class)
@@ -52,55 +60,68 @@
     private static final DevicePolicyManager sLocalDevicePolicyManager =
             sContext.getSystemService(DevicePolicyManager.class);
 
-    private RemoteDevicePolicyManager mDevicePolicyManager;
-
-    @Before
-    public void setUp() {
-        mDevicePolicyManager = sDeviceState.dpc().devicePolicyManager();
-    }
-
     @PolicyAppliesTest(policy = NearbyAppStreamingPolicy.class)
     public void getNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly() {
-        assertThat(mDevicePolicyManager.getNearbyAppStreamingPolicy())
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
+        assertThat(dpm.getNearbyAppStreamingPolicy())
                 .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
     }
 
     @PolicyAppliesTest(policy = NearbyAppStreamingPolicy.class)
     public void setNearbyAppStreamingPolicy_policyApplied_works() {
-        int originalPolicy = mDevicePolicyManager.getNearbyAppStreamingPolicy();
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+        int originalPolicy = dpm.getNearbyAppStreamingPolicy();
 
-        mDevicePolicyManager.setNearbyAppStreamingPolicy(
-                DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+        dpm.setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
 
         try {
-            assertThat(mDevicePolicyManager.getNearbyAppStreamingPolicy())
+            assertThat(dpm.getNearbyAppStreamingPolicy())
                     .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
         } finally {
-            mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+            dpm.setNearbyAppStreamingPolicy(originalPolicy);
         }
     }
 
     @CannotSetPolicyTest(policy = NearbyAppStreamingPolicy.class)
     public void setNearbyAppStreamingPolicy_policyIsNotAllowedToBeSet_throwsException() {
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
         assertThrows(SecurityException.class, () ->
-                mDevicePolicyManager.setNearbyAppStreamingPolicy(
-                        DevicePolicyManager.NEARBY_STREAMING_DISABLED));
+                dpm.setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_DISABLED));
     }
 
     @Postsubmit(reason = "new test")
     @PolicyDoesNotApplyTest(policy = NearbyAppStreamingPolicy.class)
     @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
     public void setNearbyAppStreamingPolicy_setEnabled_doesNotApply() {
-        int originalPolicy = mDevicePolicyManager.getNearbyAppStreamingPolicy();
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+        int originalPolicy = dpm.getNearbyAppStreamingPolicy();
 
-        mDevicePolicyManager
-                .setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+        dpm.setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
 
         try {
             assertThat(sLocalDevicePolicyManager.getNearbyAppStreamingPolicy()).isNotEqualTo(
                     DevicePolicyManager.NEARBY_STREAMING_ENABLED);
         } finally {
-            mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+            dpm.setNearbyAppStreamingPolicy(originalPolicy);
         }
     }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser(installInstrumentedApp = TRUE)
+    @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
+    @EnsureDoesNotHavePermission({INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void getNearbyAppStreamingPolicy_calledAcrossUsers_throwsException() {
+        DevicePolicyManager dpm;
+        try (PermissionContext p = TestApis.permissions()
+                .withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            dpm = TestApis.context()
+                    .instrumentedContextAsUser(sDeviceState.secondaryUser())
+                    .getSystemService(DevicePolicyManager.class);
+        }
+
+        assertThrows(SecurityException.class, () -> dpm.getNearbyAppStreamingPolicy());
+    }
 }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
index 559b79f..bb4113a 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
@@ -18,6 +18,10 @@
 
 import static android.Manifest.permission.READ_NEARBY_STREAMING_POLICY;
 
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.assertThrows;
@@ -28,17 +32,21 @@
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
 import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
 import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
 import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
 import com.android.bedstead.harrier.annotations.enterprise.PolicyAppliesTest;
 import com.android.bedstead.harrier.annotations.enterprise.PolicyDoesNotApplyTest;
 import com.android.bedstead.harrier.policies.NearbyNotificationStreamingPolicy;
 import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
 
-import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(BedsteadJUnit4.class)
@@ -52,38 +60,35 @@
     private static final DevicePolicyManager sLocalDevicePolicyManager =
             sContext.getSystemService(DevicePolicyManager.class);
 
-    private RemoteDevicePolicyManager mDevicePolicyManager;
-
-    @Before
-    public void setUp() {
-        mDevicePolicyManager = sDeviceState.dpc().devicePolicyManager();
-    }
-
     @PolicyAppliesTest(policy = NearbyNotificationStreamingPolicy.class)
     public void getNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly() {
-        assertThat(mDevicePolicyManager.getNearbyNotificationStreamingPolicy())
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
+        assertThat(dpm.getNearbyNotificationStreamingPolicy())
                 .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
     }
 
     @PolicyAppliesTest(policy = NearbyNotificationStreamingPolicy.class)
     public void setNearbyNotificationStreamingPolicy_policyApplied_works() {
-        int originalPolicy = mDevicePolicyManager.getNearbyNotificationStreamingPolicy();
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+        int originalPolicy = dpm.getNearbyNotificationStreamingPolicy();
 
-        mDevicePolicyManager.setNearbyNotificationStreamingPolicy(
-                DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+        dpm.setNearbyNotificationStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
 
         try {
-            assertThat(mDevicePolicyManager.getNearbyNotificationStreamingPolicy())
+            assertThat(dpm.getNearbyNotificationStreamingPolicy())
                     .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
         } finally {
-            mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+            dpm.setNearbyAppStreamingPolicy(originalPolicy);
         }
     }
 
     @CannotSetPolicyTest(policy = NearbyNotificationStreamingPolicy.class)
     public void setNearbyNotificationStreamingPolicy_policyIsNotAllowedToBeSet_throwsException() {
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
         assertThrows(SecurityException.class, () ->
-                mDevicePolicyManager.setNearbyNotificationStreamingPolicy(
+                dpm.setNearbyNotificationStreamingPolicy(
                         DevicePolicyManager.NEARBY_STREAMING_DISABLED));
     }
 
@@ -91,17 +96,34 @@
     @PolicyDoesNotApplyTest(policy = NearbyNotificationStreamingPolicy.class)
     @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
     public void setNearbyNotificationStreamingPolicy_setEnabled_doesNotApply() {
-        int originalPolicy = mDevicePolicyManager.getNearbyNotificationStreamingPolicy();
+        RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+        int originalPolicy = dpm.getNearbyNotificationStreamingPolicy();
 
-        mDevicePolicyManager
-                .setNearbyNotificationStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+        dpm.setNearbyNotificationStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
 
         try {
             assertThat(
                     sLocalDevicePolicyManager.getNearbyNotificationStreamingPolicy()).isNotEqualTo(
                     DevicePolicyManager.NEARBY_STREAMING_ENABLED);
         } finally {
-            mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+            dpm.setNearbyAppStreamingPolicy(originalPolicy);
         }
     }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser(installInstrumentedApp = TRUE)
+    @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
+    @EnsureDoesNotHavePermission({INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void getNearbyNotificationStreamingPolicy_calledAcrossUsers_throwsException() {
+        DevicePolicyManager dpm;
+        try (PermissionContext p = TestApis.permissions()
+                .withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            dpm = TestApis.context()
+                    .instrumentedContextAsUser(sDeviceState.secondaryUser())
+                    .getSystemService(DevicePolicyManager.class);
+        }
+
+        assertThrows(SecurityException.class, () -> dpm.getNearbyNotificationStreamingPolicy());
+    }
 }
diff --git a/tests/devicestate/Android.bp b/tests/devicestate/Android.bp
index a2c2523..c064c29 100644
--- a/tests/devicestate/Android.bp
+++ b/tests/devicestate/Android.bp
@@ -24,7 +24,8 @@
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "mockito-target-minus-junit4",
-        "cts-wm-util"
+        "cts-wm-util",
+        "cts_window_jetpack_utils",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java
index c1815b9..050b069 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java
@@ -18,12 +18,6 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static android.hardware.devicestate.cts.DeviceStateUtils.assertValidState;
-import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
-import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-
-import static org.junit.Assert.assertTrue;
-
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.server.wm.ActivityManagerTestBase;
@@ -31,18 +25,14 @@
 import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 import org.junit.Before;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 import javax.annotation.concurrent.GuardedBy;
 
 /**
@@ -77,12 +67,14 @@
      * {@link java.lang.InterruptedException} will be thrown.
      */
     protected final void runWithRequestActive(@NonNull DeviceStateRequest request,
+            boolean isBaseStateRequest,
             @NonNull Runnable runnable) throws Throwable {
         final UncaughtExceptionHandler exceptionHandler = new UncaughtExceptionHandler();
         final RequestAwareThread thread = new RequestAwareThread(request, runnable);
         thread.setUncaughtExceptionHandler(exceptionHandler);
-        try (DeviceStateRequestSession session
-                     = new DeviceStateRequestSession(mDeviceStateManager, request, thread)) {
+        try (DeviceStateRequestSession session =
+                     new DeviceStateRequestSession(mDeviceStateManager, request,
+                isBaseStateRequest, thread)) {
             // Set the exception handler to get the exception and rethrow.
             thread.start();
             // Wait for the request aware thread to finish executing the runnable. If the request
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
index f6ff303..09afde3 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
@@ -18,8 +18,8 @@
 
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static android.hardware.devicestate.cts.DeviceStateUtils.assertValidState;
-import static android.hardware.devicestate.cts.DeviceStateUtils.runWithControlDeviceStatePermission;
+import static android.server.wm.DeviceStateUtils.assertValidState;
+import static android.server.wm.DeviceStateUtils.runWithControlDeviceStatePermission;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertEquals;
@@ -36,6 +36,8 @@
 import android.content.res.Resources;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
+import android.server.wm.jetpack.utils.ExtensionUtil;
+import android.server.wm.jetpack.utils.Version;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -58,6 +60,9 @@
 
     private static final int INVALID_DEVICE_STATE = -1;
 
+    /** Vendor extension version. Some API behaviors are only available in newer version. */
+    private static final Version WM_EXTENSION_VERSION = ExtensionUtil.getExtensionVersion();
+
     /**
      * Tests that {@link DeviceStateManager#getSupportedStates()} returns at least one state and
      * that none of the returned states are in the range
@@ -92,13 +97,30 @@
             final DeviceStateRequest request
                     = DeviceStateRequest.newBuilder(supportedStates[i]).build();
 
-            runWithRequestActive(request, () -> {
+            runWithRequestActive(request, false, () -> {
                 verify(callback, atLeastOnce()).onStateChanged(intAgumentCaptor.capture());
                 assertEquals(intAgumentCaptor.getValue().intValue(), request.getState());
             });
         }
     }
 
+    @Test
+    public void testRequestBaseState() throws Throwable {
+        assumeExtensionVersionAtLeast2();
+        final ArgumentCaptor<Integer> intAgumentCaptor = ArgumentCaptor.forClass(Integer.class);
+        final DeviceStateManager.DeviceStateCallback callback =
+                mock(DeviceStateManager.DeviceStateCallback.class);
+        final DeviceStateManager manager = getDeviceStateManager();
+
+        manager.registerCallback(Runnable::run, callback);
+
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(0).build();
+        runWithRequestActive(request, true, () -> {
+            verify(callback, atLeastOnce()).onStateChanged(intAgumentCaptor.capture());
+            assertEquals(intAgumentCaptor.getValue().intValue(), request.getState());
+        });
+    }
+
     /**
      * Tests that calling {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
      * DeviceStateRequest.Callback)} throws an {@link java.lang.IllegalArgumentException} if
@@ -463,9 +485,7 @@
         final DeviceStateManager manager = getDeviceStateManager();
         final int[] states = manager.getSupportedStates();
         final DeviceStateRequest request = DeviceStateRequest.newBuilder(states[0]).build();
-        runWithRequestActive(request, () -> {
-            manager.cancelStateRequest();
-        });
+        runWithRequestActive(request, false, manager::cancelStateRequest);
     }
 
     /**
@@ -499,6 +519,12 @@
         }
     }
 
+    /** For API changes that are introduced together with WM Extensions version 2. */
+    private static void assumeExtensionVersionAtLeast2() {
+        // TODO(b/232476698) Remove in the next Android release.
+        assumeTrue(WM_EXTENSION_VERSION.getMajor() >= 2);
+    }
+
     private class StateTrackingCallback implements  DeviceStateManager.DeviceStateCallback {
         private int mCurrentState = - 1;
 
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java
index 7b15fd9..3e5ab31 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java
@@ -16,7 +16,7 @@
 
 package android.hardware.devicestate.cts;
 
-import static android.hardware.devicestate.cts.DeviceStateUtils.runWithControlDeviceStatePermission;
+import static android.server.wm.DeviceStateUtils.runWithControlDeviceStatePermission;
 
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
@@ -34,12 +34,15 @@
     @NonNull
     private final DeviceStateRequest mRequest;
     @NonNull
-    private DeviceStateRequest.Callback mCallback;
+    private final DeviceStateRequest.Callback mCallback;
+    private final boolean mIsBaseStateRequest;
 
     public DeviceStateRequestSession(@NonNull DeviceStateManager manager,
-            @NonNull DeviceStateRequest request, @NonNull DeviceStateRequest.Callback callback) {
+            @NonNull DeviceStateRequest request, boolean isBaseStateRequest,
+            @NonNull DeviceStateRequest.Callback callback) {
         mDeviceStateManager = manager;
         mRequest = request;
+        mIsBaseStateRequest = isBaseStateRequest;
         mCallback = callback;
 
         submitRequest(request);
@@ -47,8 +50,14 @@
 
     private void submitRequest(@NonNull DeviceStateRequest request) {
         try {
-            runWithControlDeviceStatePermission(() ->
-                    mDeviceStateManager.requestState(mRequest, Runnable::run, mCallback));
+            if (mIsBaseStateRequest) {
+                runWithControlDeviceStatePermission(() ->
+                        mDeviceStateManager.requestBaseStateOverride(mRequest, Runnable::run,
+                                mCallback));
+            } else {
+                runWithControlDeviceStatePermission(() ->
+                        mDeviceStateManager.requestState(mRequest, Runnable::run, mCallback));
+            }
         } catch (Throwable t) {
             throw new RuntimeException(t);
         }
@@ -57,7 +66,11 @@
     @Override
     public void close() {
         try {
-            runWithControlDeviceStatePermission(mDeviceStateManager::cancelStateRequest);
+            if (mIsBaseStateRequest) {
+                runWithControlDeviceStatePermission(mDeviceStateManager::cancelBaseStateOverride);
+            } else {
+                runWithControlDeviceStatePermission(mDeviceStateManager::cancelStateRequest);
+            }
         } catch (Throwable t) {
             throw new RuntimeException(t);
         }
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
index fa05e86..a981703 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
@@ -529,7 +529,6 @@
         //the enrollent
         //TODO(b/217275524)
         Thread.sleep(200);
-
         session.finishEnroll(userId);
         mInstrumentation.waitForIdleSync();
         Utils.waitForIdleService(this::getSensorStates);
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index 32a22a9..cd6aac3 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -434,6 +434,8 @@
 
         <activity android:name="android.server.wm.WindowInsetsAnimationTestBase$TestActivity"
              android:theme="@android:style/Theme.Material.NoActionBar"/>
+        <activity android:name="android.server.wm.WindowInsetsAnimationControllerTests$ControllerTestActivity"
+            android:theme="@android:style/Theme.Material.NoActionBar" />
 
         <activity android:name="android.server.wm.ForceRelayoutTestBase$TestActivity"
              android:exported="true"/>
@@ -457,6 +459,10 @@
             android:colorMode="wideColorGamut"
             android:fitsSystemWindows="true" />
 
+        <activity android:name="android.server.wm.ActivityTransitionTests$CustomWindowAnimationActivity"
+            android:theme="@style/window_task_animation"
+            android:exported="true"/>
+
         <activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
                   android:exported="true"
                   android:configChanges="screenSize|screenLayout|orientation"
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 712bf6f..a839054 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -537,6 +537,9 @@
         // Calls requestAutoEnterPictureInPicture() with the value provided
         public static final String EXTRA_ENTER_PIP_ON_PIP_REQUESTED =
                 "enter_pip_on_pip_requested";
+        // Calls enterPictureInPictureMode when activity receives onBackPressed
+        public static final String EXTRA_ENTER_PIP_ON_BACK_PRESSED =
+                "enter_pip_on_back_pressed";
         public static final String EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR =
                 "expanded_pip_numerator";
         public static final String EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR =
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index 074ab48..bc606ea 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -32,6 +32,7 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_BACK_PRESSED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
@@ -82,6 +83,7 @@
 public class PipActivity extends AbstractLifecycleLogActivity {
 
     private boolean mEnteredPictureInPicture;
+    private boolean mEnterPipOnBackPressed;
     private RemoteCallback mCb;
 
     private Handler mHandler = new Handler();
@@ -91,7 +93,13 @@
             if (intent != null) {
                 switch (intent.getAction()) {
                     case ACTION_ENTER_PIP:
-                        enterPictureInPictureMode();
+                        enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+                        if (intent.getExtras() != null) {
+                            mCb = (RemoteCallback) intent.getExtras().get(EXTRA_SET_PIP_CALLBACK);
+                            if (mCb != null) {
+                                mCb.sendResult(new Bundle());
+                            }
+                        }
                         break;
                     case ACTION_MOVE_TO_BACK:
                         moveTaskToBack(false /* nonRoot */);
@@ -293,6 +301,9 @@
             dumpConfiguration(getResources().getConfiguration());
             dumpConfigInfo();
         }
+
+        mEnterPipOnBackPressed = Boolean.parseBoolean(
+                getIntent().getStringExtra(EXTRA_ENTER_PIP_ON_BACK_PRESSED));
     }
 
     @Override
@@ -388,6 +399,15 @@
         dumpConfigInfo();
     }
 
+    @Override
+    public void onBackPressed() {
+        if (mEnterPipOnBackPressed) {
+            enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+        } else {
+            super.onBackPressed();
+        }
+    }
+
     /**
      * Launches a new instance of the PipActivity in the same task that will automatically enter
      * PiP.
diff --git a/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml b/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml
index 5a82c6b..b692bd1 100644
--- a/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml
@@ -19,6 +19,8 @@
           package="android.server.wm.jetpack"
           android:targetSandboxVersion="2">
 
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+
     <application android:label="CtsWindowManagerJetpackTestCases">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="androidx.window.extensions"
@@ -27,6 +29,7 @@
                       android:required="false" />
         <activity android:name="android.server.wm.jetpack.utils.TestActivity" />
         <activity android:name="android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity"
+                  android:supportsPictureInPicture="true"
                   android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
         />
         <activity android:name="android.server.wm.jetpack.utils.TestGetWindowLayoutInfoActivity" />
@@ -43,7 +46,10 @@
                   android:knownActivityEmbeddingCerts="6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599"
                   android:exported="true"
         />
-
+        <activity android:name="android.server.wm.jetpack.utils.TestRearDisplayActivity"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
+                  android:exported="true"
+        />
         <!-- The provider properties must match the shared one defined in the util module. -->
         <provider android:name="android.server.wm.lifecycle.EventLog"
                   android:authorities="android.server.wm.jetpack.logprovider"
diff --git a/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java b/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java
index b47e065..b9e1a8d 100644
--- a/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java
+++ b/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java
@@ -19,11 +19,11 @@
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.DEFAULT_SPLIT_RATIO;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.EMBEDDED_ACTIVITY_ID;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createSplitPairRuleBuilderWithJava8Predicate;
-import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createWildcardSplitPairRule;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityCrossUidInSplit;
 import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
 import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
 import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.EXTRA_EMBED_ACTIVITY;
+import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.EXTRA_SPLIT_RATIO;
 
 import static org.junit.Assume.assumeNotNull;
 
@@ -95,8 +95,9 @@
                 activityActivityPair -> true /* activityActivityPredicate */,
                 activityIntentPair -> true /* activityIntentPredicate */,
                 parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
-                .setSplitRatio(DEFAULT_SPLIT_RATIO).build();
-        embeddingComponent.setEmbeddingRules(Collections.singleton(createWildcardSplitPairRule()));
+                .setSplitRatio(getIntent().getFloatExtra(EXTRA_SPLIT_RATIO, DEFAULT_SPLIT_RATIO))
+                .build();
+        embeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
 
         // Launch an activity from a different UID that recognizes this package's signature and
         // verify that it is split with this activity.
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java
index 57a9fd5..2397a37 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java
@@ -23,6 +23,7 @@
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifySplit;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertNotVisible;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitForFillsTask;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
 
 import static org.junit.Assert.assertTrue;
 
@@ -227,6 +228,6 @@
         }
         return primaryActivityId.equals(((TestActivityWithId) activityIntentPair.first).getId())
                 && secondaryActivityId.equals(activityIntentPair.second.getStringExtra(
-                        ACTIVITY_ID_LABEL));
+                KEY_ACTIVITY_ID));
     }
 }
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java
index f4fa70c..e017d65 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java
@@ -97,8 +97,7 @@
         SplitPairRule splitPairRule = createWildcardSplitPairRule();
         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
 
-        TestActivity primaryActivity = (TestActivityWithId)
-                startActivityNewTask(TestActivityWithId.class);
+        TestActivity primaryActivity = startActivityNewTask(TestActivityWithId.class);
         TestActivity secondaryActivity = (TestActivity) startActivityAndVerifySplit(primaryActivity,
                 TestActivityWithId.class, splitPairRule, "secondaryActivity", mSplitInfoConsumer);
 
@@ -359,8 +358,8 @@
             mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
 
             // Launch the two activities
-            TestActivity primaryActivity = (TestActivity)
-                    startActivityNewTask(TestConfigChangeHandlingActivity.class);
+            TestActivity primaryActivity = startActivityNewTask(
+                    TestConfigChangeHandlingActivity.class);
             TestActivity secondaryActivity;
             if (mShouldPreventSideBySideActivities) {
                 secondaryActivity = startActivityAndVerifyNotSplit(primaryActivity);
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingIntegrationTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingIntegrationTests.java
new file mode 100644
index 0000000..5751177
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingIntegrationTests.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack;
+
+import static android.server.wm.jetpack.signed.Components.SIGNED_EMBEDDING_ACTIVITY;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.EMBEDDED_ACTIVITY_ID;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createSplitPairRuleBuilderWithJava8Predicate;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifyNoCallback;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifySplit;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumed;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeHasDisplayFeatures;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeVendorApiLevelAtLeast;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutComponent;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutInfo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNotNull;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.jetpack.utils.TestActivityWithId;
+import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.window.extensions.embedding.SplitPairRule;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+/**
+ * Tests for the {@link androidx.window.extensions} implementation provided on the device (and only
+ * if one is available) for the Activity Embedding functionality. Specifically tests integration
+ * with other features.
+ *
+ * Build/Install/Run:
+ *     atest CtsWindowManagerJetpackTestCases:ActivityEmbeddingIntegrationTests
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityEmbeddingIntegrationTests extends ActivityEmbeddingTestBase {
+    private WindowLayoutComponent mWindowLayoutComponent;
+
+    @Before
+    @Override
+    public void setUp() {
+        super.setUp();
+        assumeExtensionSupportedDevice();
+        mWindowLayoutComponent = getExtensionWindowLayoutComponent();
+        assumeNotNull(mWindowLayoutComponent);
+    }
+
+    /**
+     * Tests that display features are still reported when using ActivityEmbedding.
+     */
+    @Test
+    public void testDisplayFeaturesWithEmbedding() throws Exception {
+        TestConfigChangeHandlingActivity primaryActivity = startActivityNewTask(
+                TestConfigChangeHandlingActivity.class);
+        WindowLayoutInfo windowLayoutInfo = getExtensionWindowLayoutInfo(primaryActivity);
+        assumeHasDisplayFeatures(windowLayoutInfo);
+
+        // Launch a second activity in a split. Use a very small split ratio, so that the secondary
+        // activity occupies most of the screen.
+        SplitPairRule splitPairRule = createSplitPairRuleBuilderWithJava8Predicate(
+                activityActivityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true
+        )
+                .setSplitRatio(0.1f)
+                .build();
+        mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
+
+        Activity secondaryActivity = startActivityAndVerifySplit(primaryActivity,
+                TestActivityWithId.class, splitPairRule,
+                "secondaryActivity" /* secondActivityId */, mSplitInfoConsumer);
+
+        // Verify that an embedded activity still observes the same number of features
+        WindowLayoutInfo newWindowLayoutInfo = getExtensionWindowLayoutInfo(secondaryActivity);
+        assertEquals(windowLayoutInfo.getDisplayFeatures().size(),
+                newWindowLayoutInfo.getDisplayFeatures().size());
+
+        // Need to reset primary activity bounds change counter because entering the split already
+        // triggered a bounds change.
+        primaryActivity.resetBoundsChangeCounter();
+
+        // Finish the secondary activity and verify that the primary activity still receives the
+        // display features
+        secondaryActivity.finish();
+        assertTrue(primaryActivity.waitForBoundsChange());
+        assertEquals(getMaximumActivityBounds(primaryActivity),
+                getActivityBounds(primaryActivity));
+
+        newWindowLayoutInfo = getExtensionWindowLayoutInfo(primaryActivity);
+        assertEquals(windowLayoutInfo.getDisplayFeatures().size(),
+                newWindowLayoutInfo.getDisplayFeatures().size());
+    }
+
+    /**
+     * Tests that clearing the split info consumer stops notifying unregistered consumer.
+     */
+    @Test
+    public void testClearSplitInfoCallback() throws Exception {
+        assumeVendorApiLevelAtLeast(2); // TODO(b/244450254): harden the requirement in U.
+        mActivityEmbeddingComponent.clearSplitInfoCallback();
+        TestConfigChangeHandlingActivity primaryActivity = startActivityNewTask(
+                TestConfigChangeHandlingActivity.class);
+
+        // Launch a second activity in a split. Use a very small split ratio, so that the secondary
+        // activity occupies most of the screen.
+        SplitPairRule splitPairRule = createSplitPairRuleBuilderWithJava8Predicate(
+                activityActivityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true
+        )
+                .setSplitRatio(0.1f)
+                .build();
+        mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
+
+        startActivityAndVerifyNoCallback(primaryActivity,
+                TestActivityWithId.class,
+                "secondaryActivity" /* secondActivityId */,
+                mSplitInfoConsumer);
+    }
+
+    /**
+     * Tests that display features are still reported when using ActivityEmbedding. Same as above,
+     * but using different packages for the host and embedded activities.
+     * Fixed in CL: If2dbc337c4b8cb909914cc28ae4db28a82ff9de3
+     */
+    @Test
+    public void testDisplayFeaturesWithEmbedding_differentPackage() throws Exception {
+        // Start an activity to collect the window layout info.
+        TestConfigChangeHandlingActivity initialActivity = startActivityNewTask(
+                TestConfigChangeHandlingActivity.class);
+        WindowLayoutInfo windowLayoutInfo = getExtensionWindowLayoutInfo(initialActivity);
+        assumeHasDisplayFeatures(windowLayoutInfo);
+
+        // Start an activity that will attempt to embed TestActivityKnownEmbeddingCerts. It will be
+        // put in a new task, since it has a different affinity.
+        Bundle extras = new Bundle();
+        extras.putBoolean(EXTRA_EMBED_ACTIVITY, true);
+        extras.putFloat(EXTRA_SPLIT_RATIO, 0.1f);
+        startActivityNoWait(mContext, SIGNED_EMBEDDING_ACTIVITY, extras);
+
+        waitAndAssertResumed(EMBEDDED_ACTIVITY_ID);
+        TestActivityWithId secondaryActivity = getResumedActivityById(EMBEDDED_ACTIVITY_ID);
+        assertNotNull(secondaryActivity);
+        assertTrue(mActivityEmbeddingComponent.isActivityEmbedded(secondaryActivity));
+
+        // Verify that an embedded activity from a different package observes the same number of
+        // features as the initial one.
+        WindowLayoutInfo newWindowLayoutInfo = getExtensionWindowLayoutInfo(secondaryActivity);
+        assertEquals(windowLayoutInfo.getDisplayFeatures().size(),
+                newWindowLayoutInfo.getDisplayFeatures().size());
+    }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java
index 7e72571..e2fc36b 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java
@@ -140,7 +140,7 @@
                 TestActivityWithId2.class, splitPairRule,
                 "secondaryActivity2" /* secondActivityId */, mSplitInfoConsumer);
         List<Pair<String, String>> expected2 = List.of(
-                transition(TestActivityWithId.class, ON_DESTROY),
+                transition(TestActivityWithId.class, ON_PAUSE),
                 transition(TestActivityWithId2.class, ON_CREATE),
                 transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED));
         assertTrue("Replace secondary container activity",
@@ -148,6 +148,8 @@
                         checkOrder(mEventLog, expected2)));
         waitAndAssertResumed(primaryActivity);
         waitAndAssertResumed(secondaryActivity2);
+        // Destroy may happen after the secondaryActivity2 becomes visible and IDLE.
+        waitAndAssertActivityOnDestroy(TestActivityWithId.class);
     }
 
     /**
@@ -251,12 +253,8 @@
 
         // Finish secondary activity
         secondaryActivity.finish();
-        assertTrue("Split state change must be observed",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
-        assertTrue("Secondary activity must be finished",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TestActivityWithId.class, ON_DESTROY))));
+        waitAndAssertSplitStatesUpdated();
+        waitAndAssertActivityOnDestroy(TestActivityWithId.class);
         waitAndAssertResumed(primaryActivity);
     }
 
@@ -284,14 +282,16 @@
         // Finish secondary activity, should trigger finishing of the primary as well
         secondaryActivity.finish();
         List<Pair<String, String>> expected = List.of(
-                transition(TestActivityWithId.class, ON_DESTROY),
-                transition(TestConfigChangeHandlingActivity.class, ON_DESTROY));
+                transition(TestActivityWithId.class, ON_PAUSE),
+                transition(TestConfigChangeHandlingActivity.class, ON_PAUSE));
         assertTrue("Finish secondary activity with dependents",
                 mLifecycleTracker.waitForConditionWithTimeout(() ->
                         checkOrder(mEventLog, expected)));
-        assertTrue("Split state change must be observed",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+        // There is no guarantee on the order, because the removal may be delayed until the next
+        // resumed becomes visible.
+        waitAndAssertActivityOnDestroy(TestConfigChangeHandlingActivity.class);
+        waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+        waitAndAssertSplitStatesUpdated();
     }
 
     /**
@@ -317,12 +317,8 @@
 
         // Finish primary activity
         primaryActivity.finish();
-        assertTrue("Split state change must be observed",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
-        assertTrue("Primary activity must be finished",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TestConfigChangeHandlingActivity.class, ON_DESTROY))));
+        waitAndAssertSplitStatesUpdated();
+        waitAndAssertActivityOnDestroy(TestConfigChangeHandlingActivity.class);
         waitAndAssertResumed(secondaryActivity);
     }
 
@@ -349,14 +345,16 @@
         // Finish primary activity should trigger finishing of the secondary as well.
         primaryActivity.finish();
         List<Pair<String, String>> expected = List.of(
-                transition(TestActivityWithId.class, ON_DESTROY),
-                transition(TestConfigChangeHandlingActivity.class, ON_DESTROY));
+                transition(TestConfigChangeHandlingActivity.class, ON_PAUSE),
+                transition(TestActivityWithId.class, ON_PAUSE));
         assertTrue("Finish primary activity with dependents",
                 mLifecycleTracker.waitForConditionWithTimeout(() ->
                         checkOrder(mEventLog, expected)));
-        assertTrue("Split state change must be observed",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+        // There is no guarantee on the order, because the removal may be delayed until the next
+        // resumed becomes visible.
+        waitAndAssertActivityOnDestroy(TestConfigChangeHandlingActivity.class);
+        waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+        waitAndAssertSplitStatesUpdated();
     }
 
     /**
@@ -389,12 +387,8 @@
 
         // Finish the last activity
         secondaryActivity2.finish();
-        assertTrue("Split state change must be observed",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
-        assertTrue("Last activity must be finished",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TestActivityWithId2.class, ON_DESTROY))));
+        waitAndAssertSplitStatesUpdated();
+        waitAndAssertActivityOnDestroy(TestActivityWithId2.class);
         waitAndAssertResumed(primaryActivity);
     }
 
@@ -432,14 +426,15 @@
         waitAndAssertResumed(secondaryActivity2);
         waitAndAssertNotVisible(primaryActivity);
         List<Pair<String, String>> expected = List.of(
-                transition(TestActivityWithId.class, ON_DESTROY),
+                transition(TestActivityWithId.class, ON_PAUSE),
                 transition(TestConfigChangeHandlingActivity.class, ON_STOP));
         assertTrue("Finish middle activity in multi-split",
                 mLifecycleTracker.waitForConditionWithTimeout(() ->
                         checkOrder(mEventLog, expected)));
-        assertTrue("Split state change must be observed",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+        // There is no guarantee on the order, because the removal may be delayed until the next
+        // resumed becomes visible.
+        waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+        waitAndAssertSplitStatesUpdated();
     }
 
     /**
@@ -473,14 +468,15 @@
         waitAndAssertResumed(secondaryActivity2);
         waitAndAssertNotVisible(primaryActivity);
         List<Pair<String, String>> expected = List.of(
-                transition(TestActivityWithId.class, ON_DESTROY),
+                transition(TestActivityWithId.class, ON_PAUSE),
                 transition(TestConfigChangeHandlingActivity.class, ON_STOP));
         assertTrue("Finish middle activity in multi-split",
                 mLifecycleTracker.waitForConditionWithTimeout(() ->
                         checkOrder(mEventLog, expected)));
-        assertTrue("Split state change must be observed",
-                mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
-                        transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+        // There is no guarantee on the order, because the removal may be delayed until the next
+        // resumed becomes visible.
+        waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+        waitAndAssertSplitStatesUpdated();
     }
 
     /**
@@ -515,12 +511,18 @@
         // Finish the middle activity
         secondaryActivity.finish();
         waitAndAssertResumed(primaryActivity);
-        List<Pair<String, String>> expected = List.of(
-                transition(TestActivityWithId2.class, ON_DESTROY),
-                transition(TestActivityWithId.class, ON_DESTROY));
-        assertTrue("Finish middle activity in multi-split with dependents",
-                mLifecycleTracker.waitForConditionWithTimeout(() ->
-                        checkOrder(mEventLog, expected)));
+        // There is no guarantee on the order, because the removal may be delayed until the next
+        // resumed becomes visible.
+        waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+        waitAndAssertActivityOnDestroy(TestActivityWithId2.class);
+        waitAndAssertSplitStatesUpdated();
+    }
+
+    private void waitAndAssertActivityOnDestroy(Class<? extends Activity> activityClass) {
+        mLifecycleTracker.waitAndAssertActivityCurrentState(activityClass, ON_DESTROY);
+    }
+
+    private void waitAndAssertSplitStatesUpdated() {
         assertTrue("Split state change must be observed",
                 mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
                         transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java
index 0a105cf..e367704 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java
@@ -22,6 +22,7 @@
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertFinishing;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertNotResumed;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumed;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
 
 import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
 
@@ -319,7 +320,7 @@
         public SplitPlaceholderRule build() {
             // Create placeholder activity intent
             Intent placeholderIntent = new Intent(mContext, TestActivityWithId.class);
-            placeholderIntent.putExtra(ACTIVITY_ID_LABEL, mPlaceholderActivityId);
+            placeholderIntent.putExtra(KEY_ACTIVITY_ID, mPlaceholderActivityId);
 
             // Create {@link SplitPlaceholderRule} that launches the placeholder in a split with the
             // target primary activity.
@@ -329,7 +330,8 @@
                                     && mPrimaryActivityId.equals(((TestActivityWithId) activity)
                                     .getId()) /* activityPredicate */,
                             intent -> mPrimaryActivityId.equals(
-                                    intent.getStringExtra(ACTIVITY_ID_LABEL)) /* intentPredicate */,
+                                    intent.getStringExtra(KEY_ACTIVITY_ID)
+                            ) /* intentPredicate */,
                     mParentWindowMetricsPredicate)
                     .setSplitRatio(DEFAULT_SPLIT_RATIO);
 
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java
index a929e86..5956402 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java
@@ -16,6 +16,8 @@
 
 package android.server.wm.jetpack;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.jetpack.second.Components.SECOND_UNTRUSTED_EMBEDDING_ACTIVITY;
 import static android.server.wm.jetpack.signed.Components.SIGNED_EMBEDDING_ACTIVITY;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createWildcardSplitPairRule;
@@ -23,7 +25,6 @@
 import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
 import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.EXTRA_EMBED_ACTIVITY;
 import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityFromActivity;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityNewTask;
 import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityOnDisplaySingleTop;
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -37,11 +38,13 @@
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
 import android.server.wm.ActivityManagerTestBase;
 import android.server.wm.Condition;
 import android.server.wm.NestedShellPermission;
 import android.server.wm.WindowManagerState;
 import android.server.wm.jetpack.utils.TestActivityKnownEmbeddingCerts;
+import android.server.wm.jetpack.utils.TestActivityLauncher;
 import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
 
 import androidx.annotation.NonNull;
@@ -50,6 +53,8 @@
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
 import androidx.window.extensions.embedding.SplitPairRule;
 
+import com.android.compatibility.common.util.ApiTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -66,6 +71,7 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerJetpackTestCases:ActivityEmbeddingPolicyTests
  */
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ActivityEmbeddingPolicyTests extends ActivityManagerTestBase {
     protected ActivityEmbeddingComponent mActivityEmbeddingComponent;
@@ -74,8 +80,6 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        // TODO(b/207070762): remove the assumption after shell transition enabled.
-        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         assumeExtensionSupportedDevice();
         WindowExtensions windowExtensions = getWindowExtensions();
         assumeNotNull(windowExtensions);
@@ -94,10 +98,20 @@
      * Verifies that all input is dropped for activities that are embedded and being animated with
      * untrusted embedding.
      */
+    @ApiTest(apis = {"com.android.server.wm.ActivityRecord#setDropInputForAnimation",
+            "androidx.window.extensions.embedding.ActivityEmbeddingComponent#setEmbeddingRules"})
     @Test
     public void testInputDuringAnimationIsNotAllowed_untrustedEmbedding() {
-        Activity primaryActivity = startActivityNewTask(mContext, mInstrumentation,
-                TestConfigChangeHandlingActivity.class, null /* activityId */);
+        // TODO(b/207070762): remove the test when cleanup legacy app transition
+        // We don't need to disable input with Shell transition, because we won't pass the surface
+        // to app.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
+
+        Activity primaryActivity = new TestActivityLauncher<>(mContext,
+                TestConfigChangeHandlingActivity.class)
+                .addIntentFlag(FLAG_ACTIVITY_NEW_TASK)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .launch(mInstrumentation);
 
         SplitPairRule splitPairRule = createWildcardSplitPairRule(true /* shouldClearTop */);
         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
@@ -139,6 +153,11 @@
      */
     @Test
     public void testInputDuringAnimationIsNotAllowed_trustedEmbedding() {
+        // TODO(b/207070762): remove the test when cleanup legacy app transition
+        // We don't need to disable input with Shell transition, because we won't pass the surface
+        // to app.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
+
         // Extend the animation scale, so that the test has enough time to catch the state during
         // transition.
         UiAutomation automation = mInstrumentation.getUiAutomation();
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java
index e50cd40..5f5263f 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java
@@ -19,11 +19,10 @@
 import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
 import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
 
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeNotNull;
 
-import android.os.SystemProperties;
 import android.server.wm.ActivityManagerTestBase.ReportedDisplayMetrics;
+import android.server.wm.UiDeviceUtils;
 import android.server.wm.jetpack.utils.JavaConsumerAdapter;
 import android.server.wm.jetpack.utils.TestValueCountConsumer;
 import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
@@ -48,15 +47,11 @@
     protected TestValueCountConsumer<List<SplitInfo>> mSplitInfoConsumer;
     protected ReportedDisplayMetrics mReportedDisplayMetrics =
             ReportedDisplayMetrics.getDisplayMetrics(Display.DEFAULT_DISPLAY);
-    private static final boolean ENABLE_SHELL_TRANSITIONS =
-            SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
 
     @Override
     @Before
     public void setUp() {
         super.setUp();
-        // TODO(b/207070762): remove the assumption after shell transition enabled.
-        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         assumeExtensionSupportedDevice();
         WindowExtensions windowExtensions = getWindowExtensions();
         assumeNotNull(windowExtensions);
@@ -70,6 +65,9 @@
                     new JavaConsumerAdapter<>(mSplitInfoConsumer)
             );
         }
+
+        UiDeviceUtils.pressWakeupButton();
+        UiDeviceUtils.pressUnlockButton();
     }
 
     @After
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionRearDisplayTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionRearDisplayTest.java
new file mode 100644
index 0000000..8a6df77
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionRearDisplayTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack;
+
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.PollingCheck.waitFor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.DeviceStateUtils;
+import android.server.wm.jetpack.utils.TestRearDisplayActivity;
+import android.server.wm.jetpack.utils.WindowExtensionTestRule;
+import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.area.WindowAreaComponent.WindowAreaSessionState;
+import androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus;
+import androidx.window.extensions.core.util.function.Consumer;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Tests for the {@link androidx.window.extensions.area.WindowAreaComponent} implementation
+ * of the rear display functionality provided on the device (and only if one is available).
+ *
+ * Build/Install/Run:
+ * atest CtsWindowManagerJetpackTestCases:ExtensionRearDisplayTest
+ */
+@LargeTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ExtensionRearDisplayTest extends WindowManagerJetpackTestBase implements
+        DeviceStateManager.DeviceStateCallback {
+
+    private static final int TIMEOUT = 2000;
+    private static final int INVALID_DEVICE_STATE = -1;
+
+    private TestRearDisplayActivity mActivity;
+    private WindowAreaComponent mWindowAreaComponent;
+    private int mCurrentDeviceState;
+    private int mCurrentDeviceBaseState;
+    private int[] mSupportedDeviceStates;
+    @WindowAreaStatus
+    private Integer mWindowAreaStatus;
+    @WindowAreaSessionState
+    private Integer mWindowAreaSessionState;
+    private int mRearDisplayState;
+
+    private final Context mInstrumentationContext = getInstrumentation().getTargetContext();
+    private final KeyguardManager mKeyguardManager = mInstrumentationContext.getSystemService(
+            KeyguardManager.class);
+    private final DeviceStateManager mDeviceStateManager = mInstrumentationContext
+            .getSystemService(DeviceStateManager.class);
+
+    private final Consumer<Integer> mStatusListener = (status) -> mWindowAreaStatus = status;
+
+    private final Consumer<Integer> mSessionStateListener = (sessionState) -> {
+        mWindowAreaSessionState = sessionState;
+    };
+
+    @Rule
+    public final WindowExtensionTestRule mWindowManagerJetpackTestRule =
+            new WindowExtensionTestRule(WindowAreaComponent.class);
+
+    @Before
+    @Override
+    public void setUp() {
+        super.setUp();
+        mWindowAreaComponent =
+                (WindowAreaComponent) mWindowManagerJetpackTestRule.getExtensionComponent();
+        mSupportedDeviceStates = mDeviceStateManager.getSupportedStates();
+        assumeTrue(mSupportedDeviceStates.length > 1);
+        // TODO(b/236022708) Move rear display state to device state config file
+        mRearDisplayState = getInstrumentation().getTargetContext().getResources()
+                .getInteger(Resources.getSystem()
+                        .getIdentifier("config_deviceStateRearDisplay", "integer", "android"));
+        assumeTrue(mRearDisplayState != INVALID_DEVICE_STATE);
+        mDeviceStateManager.registerCallback(Runnable::run, this);
+        mWindowAreaComponent.addRearDisplayStatusListener(mStatusListener);
+        unlockDeviceIfNeeded();
+        mActivity = startActivityNewTask(TestRearDisplayActivity.class);
+        waitAndAssert(() -> mWindowAreaStatus != null);
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+        if (mWindowAreaComponent != null) {
+            mWindowAreaComponent.removeRearDisplayStatusListener(mStatusListener);
+            try {
+                DeviceStateUtils.runWithControlDeviceStatePermission(
+                        () -> mDeviceStateManager.cancelStateRequest());
+            } catch (Throwable t) {
+                throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Tests that the RearDisplay status listeners receive the correct {@link WindowAreaStatus}
+     * values.
+     *
+     * The test goes through all supported device states and verifies that the correct status is
+     * returned. If the state does not place the device in the active RearDisplay configuration
+     * (i.e. the base state of the device is different than the current state, and that current
+     * state is the RearDisplay state), then it should receive the
+     * {@link WindowAreaStatus#STATUS_AVAILABLE} value, otherwise it should receive the
+     * {@link WindowAreaStatus#STATUS_UNAVAILABLE} value.
+     */
+    @ApiTest(apis = {
+            "androidx.window.extensions.area.WindowAreaComponent#addRearDisplayStatusListener",
+            "androidx.window.extensions.area.WindowAreaComponent#removeRearDisplayStatusListener"})
+    @Test
+    public void testRearDisplayStatusListeners() throws Throwable {
+        Set<Integer> requestedStates = new HashSet<>();
+        while (requestedStates.size() != mSupportedDeviceStates.length) {
+            int newState = determineNewState(mCurrentDeviceState, mSupportedDeviceStates,
+                    requestedStates);
+            if (newState != INVALID_DEVICE_STATE) {
+                requestedStates.add(newState);
+                DeviceStateRequest request = DeviceStateRequest.newBuilder(newState).build();
+                DeviceStateUtils.runWithControlDeviceStatePermission(() ->
+                            mDeviceStateManager.requestState(request, null, null));
+
+                waitAndAssert(() -> mCurrentDeviceState == newState);
+                // If the state does not put the device into the rear display configuration,
+                // then the listener should receive the STATUS_AVAILABLE value.
+                if (!isRearDisplayActive(mCurrentDeviceState, mCurrentDeviceBaseState)) {
+                    waitAndAssert(
+                            () -> mWindowAreaStatus == WindowAreaComponent.STATUS_AVAILABLE);
+                } else {
+                    waitAndAssert(
+                            () -> mWindowAreaStatus == WindowAreaComponent.STATUS_UNAVAILABLE);
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests that you can start and end rear display mode. Verifies that the {@link Consumer} that
+     * is provided when calling {@link WindowAreaComponent#startRearDisplaySession} receives
+     * the {@link WindowAreaSessionState#SESSION_STATE_ACTIVE} value when starting the session
+     * and {@link WindowAreaSessionState#SESSION_STATE_INACTIVE} when ending the session.
+     *
+     * This test also verifies that the {@link android.app.Activity} is still visible when rear
+     * display mode is started, and that the activity received a configuration change when enabling
+     * and disabling rear display mode. This is verifiable due to the current generation of
+     * hardware and the fact that there are different screen sizes from the different displays.
+     */
+    @ApiTest(apis = {
+            "androidx.window.extensions.area.WindowAreaComponent#startRearDisplaySession",
+            "androidx.window.extensions.area.WindowAreaComponent#endRearDisplaySession"})
+    @Test
+    public void testStartAndEndRearDisplaySession() throws Throwable {
+        assumeTrue(mWindowAreaStatus == WindowAreaComponent.STATUS_AVAILABLE);
+        assumeTrue(mCurrentDeviceState != mRearDisplayState);
+
+        mActivity.mConfigurationChanged = false;
+        // Running with CONTROL_DEVICE_STATE permission to bypass educational overlay
+        DeviceStateUtils.runWithControlDeviceStatePermission(() ->
+                mWindowAreaComponent.startRearDisplaySession(mActivity, mSessionStateListener));
+        waitAndAssert(() -> mActivity.mConfigurationChanged);
+        assertTrue(mWindowAreaSessionState != null
+                        && mWindowAreaSessionState == WindowAreaComponent.SESSION_STATE_ACTIVE);
+        assertEquals(mCurrentDeviceState, mRearDisplayState);
+        assertTrue(isActivityVisible(mActivity));
+
+        mActivity.mConfigurationChanged = false;
+        mWindowAreaComponent.endRearDisplaySession();
+        waitAndAssert(() -> WindowAreaComponent.SESSION_STATE_INACTIVE == mWindowAreaSessionState);
+        waitAndAssert(() -> mActivity.mConfigurationChanged);
+        assertTrue(isActivityVisible(mActivity));
+        // Cancelling rear display mode should cancel the override, so verifying that the
+        // device state is the same as the physical state of the device.
+        assertEquals(mCurrentDeviceState, mCurrentDeviceBaseState);
+        assertEquals(WindowAreaComponent.STATUS_AVAILABLE, (int) mWindowAreaStatus);
+
+    }
+
+    @Override
+    public void onBaseStateChanged(int state) {
+        mCurrentDeviceBaseState = state;
+    }
+
+    @Override
+    public void onStateChanged(int state) {
+        mCurrentDeviceState = state;
+    }
+
+    /**
+     * Returns the next state that we should request that isn't the current state and
+     * has not already been requested.
+     */
+    private int determineNewState(int currentDeviceState, int[] statesToRequest,
+            Set<Integer> requestedStates) {
+        for (int state : statesToRequest) {
+            if (state != currentDeviceState && !requestedStates.contains(state)) {
+                return state;
+            }
+        }
+        return INVALID_DEVICE_STATE;
+    }
+
+    /**
+     * Helper method to determine if a rear display session is currently active by checking
+     * if the current device configuration matches that of rear display. This would be true
+     * if there is a device override currently active (base state != current state) and the current
+     * state is that which corresponds to {@code mRearDisplayState}
+     * @return {@code true} if the device is in rear display mode and {@code false} if not
+     */
+    private boolean isRearDisplayActive(int currentDeviceState, int currentDeviceBaseState) {
+        return (currentDeviceState != currentDeviceBaseState)
+                && (currentDeviceState == mRearDisplayState);
+    }
+
+    private void unlockDeviceIfNeeded() {
+        if (isKeyguardLocked() || !Objects.requireNonNull(
+                mInstrumentationContext.getSystemService(PowerManager.class)).isInteractive()) {
+            pressWakeupButton();
+            pressUnlockButton();
+        }
+    }
+
+    private boolean isKeyguardLocked() {
+        return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+    }
+
+    private void waitAndAssert(PollingCheck.PollingCheckCondition condition) {
+        waitFor(TIMEOUT, condition);
+    }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java
index 4326f7d..95036cb 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java
@@ -18,13 +18,14 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.server.wm.jetpack.utils.ExtensionUtil.EXTENSION_VERSION_2;
 import static android.server.wm.jetpack.utils.ExtensionUtil.assertEqualWindowLayoutInfo;
-import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
 import static android.server.wm.jetpack.utils.ExtensionUtil.assumeHasDisplayFeatures;
-import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutComponent;
 import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutInfo;
+import static android.server.wm.jetpack.utils.ExtensionUtil.isExtensionVersionAtLeast;
 import static android.server.wm.jetpack.utils.SidecarUtil.assumeSidecarSupportedDevice;
 import static android.server.wm.jetpack.utils.SidecarUtil.getSidecarInterface;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static androidx.window.extensions.layout.FoldingFeature.STATE_FLAT;
 import static androidx.window.extensions.layout.FoldingFeature.STATE_HALF_OPENED;
@@ -35,17 +36,28 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
 
+import android.content.Context;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.Presubmit;
 import android.server.wm.IgnoreOrientationRequestSession;
 import android.server.wm.jetpack.utils.TestActivity;
 import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
 import android.server.wm.jetpack.utils.TestValueCountJavaConsumer;
+import android.server.wm.jetpack.utils.WindowExtensionTestRule;
 import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.window.extensions.layout.DisplayFeature;
 import androidx.window.extensions.layout.FoldingFeature;
@@ -54,10 +66,13 @@
 import androidx.window.sidecar.SidecarDisplayFeature;
 import androidx.window.sidecar.SidecarInterface;
 
+import com.android.compatibility.common.util.ApiTest;
+
 import com.google.common.collect.BoundType;
 import com.google.common.collect.Range;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -74,6 +89,7 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerJetpackTestCases:ExtensionWindowLayoutComponentTest
  */
+@Presubmit
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class ExtensionWindowLayoutComponentTest extends WindowManagerJetpackTestBase {
@@ -82,20 +98,47 @@
     private WindowLayoutComponent mWindowLayoutComponent;
     private WindowLayoutInfo mWindowLayoutInfo;
 
+    @Rule
+    public final WindowExtensionTestRule mWindowExtensionTestRule =
+            new WindowExtensionTestRule(WindowLayoutComponent.class);
+
     @Before
     @Override
     public void setUp() {
         super.setUp();
-        assumeExtensionSupportedDevice();
-        mActivity = (TestActivity) startActivityNewTask(TestActivity.class);
-        mWindowLayoutComponent = getExtensionWindowLayoutComponent();
+        mWindowLayoutComponent =
+                (WindowLayoutComponent) mWindowExtensionTestRule.getExtensionComponent();
         assumeNotNull(mWindowLayoutComponent);
+        mActivity = startActivityNewTask(TestActivity.class);
+    }
+
+    private Context createContextWithNonActivityWindow() {
+        Display defaultDisplay = mContext.getSystemService(DisplayManager.class).getDisplay(
+                DEFAULT_DISPLAY);
+        Context windowContext = mContext.createWindowContext(defaultDisplay,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+
+        mInstrumentation.runOnMainSync(() -> {
+            final View view = new View(windowContext);
+            WindowManager wm = windowContext.getSystemService(WindowManager.class);
+            WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+            wm.addView(view, params);
+        });
+        return windowContext;
+    }
+
+    private void assumeExtensionVersionSupportsWindowContextLayout() {
+        assumeTrue("This test should only be run on devices with version: ",
+                isExtensionVersionAtLeast(EXTENSION_VERSION_2));
     }
 
     /**
      * Test adding and removing a window layout change listener.
      */
     @Test
+    @ApiTest(apis = {
+            "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
     public void testWindowLayoutComponent_onWindowLayoutChangeListener() throws Exception {
         // Set activity to portrait
         setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
@@ -106,29 +149,36 @@
         // removed.
         TestValueCountJavaConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
                 new TestValueCountJavaConsumer<>();
-        windowLayoutInfoConsumer.setCount(2);
+        windowLayoutInfoConsumer.setCount(1);
 
         // Add window layout listener for mWindowToken - onWindowLayoutChanged should be called
         mWindowLayoutComponent.addWindowLayoutInfoListener(mActivity, windowLayoutInfoConsumer);
+        // Initial registration invokes a consumer callback synchronously, clear the queue to
+        // make sure there's no residual value or from the first orientation change.
+        windowLayoutInfoConsumer.clearQueue();
 
         // Change the activity orientation - onWindowLayoutChanged should be called
         setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
                 ORIENTATION_LANDSCAPE);
 
+        // Check we have received exactly one layout update.
+        assertNotNull(windowLayoutInfoConsumer.waitAndGet());
+
         // Remove the listener
         mWindowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
+        windowLayoutInfoConsumer.clearQueue();
 
         // Change the activity orientation - onWindowLayoutChanged should NOT be called
         setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
                 ORIENTATION_PORTRAIT);
-
-        // Check that the countdown is zero
         WindowLayoutInfo lastValue = windowLayoutInfoConsumer.waitAndGet();
-        assertNotNull(lastValue);
+        assertNull(lastValue);
     }
 
     @Test
-    public void testWindowLayoutComponent_WindowLayoutInfoListener() {
+    @ApiTest(apis = {
+            "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+    public void testWindowLayoutComponent_windowLayoutInfoListener() {
         TestValueCountJavaConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
                 new TestValueCountJavaConsumer<>();
         // Test that adding and removing callback succeeds
@@ -136,8 +186,9 @@
         mWindowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
     }
 
+    @ApiTest(apis = {"androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
     @Test
-    public void testDisplayFeatures()
+    public void testWindowLayoutComponent_providesWindowLayoutFromActivity()
             throws ExecutionException, InterruptedException, TimeoutException {
         mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
         assumeHasDisplayFeatures(mWindowLayoutInfo);
@@ -167,44 +218,153 @@
     }
 
     @Test
+    @ApiTest(apis = {
+            "androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
+    public void testWindowLayoutComponent_providesWindowLayoutFromWindowContext()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        assumeExtensionVersionSupportsWindowContextLayout();
+        Context windowContext = createContextWithNonActivityWindow();
+
+        mWindowLayoutInfo = getExtensionWindowLayoutInfo(windowContext);
+        assumeHasDisplayFeatures(mWindowLayoutInfo);
+
+        // Verify that window layouts and metrics are reasonable.
+        WindowManager mWm = windowContext.getSystemService(WindowManager.class);
+        final WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics();
+
+        for (DisplayFeature displayFeature : mWindowLayoutInfo.getDisplayFeatures()) {
+            final Rect featureRect = displayFeature.getBounds();
+            assertHasNonNegativeDimensions(featureRect);
+            assertNotBothDimensionsZero(featureRect);
+            assertTrue(currentMetrics.getBounds().contains(featureRect));
+        }
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+    public void testWindowLayoutComponent_windowLayoutMatchesBetweenActivityAndWindowContext()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        assumeExtensionVersionSupportsWindowContextLayout();
+        Context windowContext = createContextWithNonActivityWindow();
+
+        WindowLayoutInfo windowLayoutInfoFromContext = getExtensionWindowLayoutInfo(windowContext);
+
+        TestConfigChangeHandlingActivity configHandlingActivity = startFullScreenActivityNewTask(
+                        TestConfigChangeHandlingActivity.class, null);
+        WindowLayoutInfo windowLayoutInfoFromActivity = getExtensionWindowLayoutInfo(
+                configHandlingActivity);
+
+        assertEquals(windowLayoutInfoFromContext, windowLayoutInfoFromActivity);
+    }
+
+    @ApiTest(apis = {"androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
+    @Test
     public void testGetWindowLayoutInfo_configChanged_windowLayoutUpdates()
             throws InterruptedException {
+        assumeSupportsRotation();
         mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
         assumeHasDisplayFeatures(mWindowLayoutInfo);
 
-        final TestConfigChangeHandlingActivity configHandlingActivity
-                = (TestConfigChangeHandlingActivity) startActivityNewTask(
-                TestConfigChangeHandlingActivity.class);
+        final TestConfigChangeHandlingActivity activity = startFullScreenActivityNewTask(
+                        TestConfigChangeHandlingActivity.class, null);
 
         try (IgnoreOrientationRequestSession session =
-                    new IgnoreOrientationRequestSession(false /* enable */)) {
-            setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity,
+                     new IgnoreOrientationRequestSession(false /* enable */)) {
+            setActivityOrientationActivityHandlesOrientationChanges(activity,
                     ORIENTATION_PORTRAIT);
             final WindowLayoutInfo portraitWindowLayoutInfo = getExtensionWindowLayoutInfo(
-                    configHandlingActivity);
-            final Rect portraitBounds = getActivityBounds(configHandlingActivity);
-            final Rect portraitMaximumBounds = getMaximumActivityBounds(configHandlingActivity);
+                    activity);
+            final Rect portraitBounds = getActivityBounds(activity);
+            final Rect portraitMaximumBounds = getMaximumActivityBounds(activity);
 
-            setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity,
+            setActivityOrientationActivityHandlesOrientationChanges(activity,
                     ORIENTATION_LANDSCAPE);
             final WindowLayoutInfo landscapeWindowLayoutInfo = getExtensionWindowLayoutInfo(
-                    configHandlingActivity);
-            final Rect landscapeBounds = getActivityBounds(configHandlingActivity);
-            final Rect landscapeMaximumBounds = getMaximumActivityBounds(configHandlingActivity);
+                    activity);
+            final Rect landscapeBounds = getActivityBounds(activity);
+            final Rect landscapeMaximumBounds = getMaximumActivityBounds(activity);
 
             final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
                     portraitMaximumBounds, landscapeMaximumBounds);
-            assertTrue(doesDisplayRotateForOrientation);
             assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo,
                     portraitBounds, landscapeBounds, doesDisplayRotateForOrientation);
         }
     }
 
+    @ApiTest(apis = {"androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
     @Test
-    public void testGetWindowLayoutInfo_windowRecreated_windowLayoutUpdates()
+    public void testGetWindowLayoutInfo_enterExitPip_windowLayoutInfoMatches()
             throws InterruptedException {
+        mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
+        assumeHasDisplayFeatures(mWindowLayoutInfo);
+
+        TestConfigChangeHandlingActivity configHandlingActivity = startActivityNewTask(
+                        TestConfigChangeHandlingActivity.class, null);
+
+        final WindowLayoutInfo initialInfo = getExtensionWindowLayoutInfo(
+                configHandlingActivity);
+
+        enterPipActivityHandlesConfigChanges(configHandlingActivity);
+        exitPipActivityHandlesConfigChanges(configHandlingActivity);
+
+        final WindowLayoutInfo updatedInfo = getExtensionWindowLayoutInfo(
+                configHandlingActivity);
+
+        assertEquals(initialInfo, updatedInfo);
+    }
+
+    /*
+     * Similar to #testGetWindowLayoutInfo_configChanged_windowLayoutUpdates, here we trigger
+     * rotations with a full screen activity on one Display Area, verify that WindowLayoutInfo
+     * are updated with callbacks.
+     */
+    @FlakyTest(bugId = 254056760)
+    @Test
+    @ApiTest(apis = {
+            "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener",
+            "androidx.window.extensions.layout.WindowLayoutComponent#removeWindowLayoutInfoListener"
+    })
+    public void testWindowLayoutComponent_updatesWindowLayoutFromContextAfterRotation()
+            throws InterruptedException {
+        assumeExtensionVersionSupportsWindowContextLayout();
+        assumeSupportsRotation();
+
         try (IgnoreOrientationRequestSession session =
-                    new IgnoreOrientationRequestSession(false /* enable */)) {
+                     new IgnoreOrientationRequestSession(false /* enable */)) {
+            final TestConfigChangeHandlingActivity activity = startFullScreenActivityNewTask(
+                    TestConfigChangeHandlingActivity.class, null);
+
+            setActivityOrientationActivityHandlesOrientationChanges(activity,
+                    ORIENTATION_PORTRAIT);
+
+            WindowLayoutInfo firstWindowLayout = getExtensionWindowLayoutInfo(activity);
+            final Rect firstBounds = getActivityBounds(activity);
+            final Rect firstMaximumBounds = getMaximumActivityBounds(activity);
+
+            setActivityOrientationActivityHandlesOrientationChanges(activity,
+                    ORIENTATION_LANDSCAPE);
+
+            WindowLayoutInfo secondWindowLayout = getExtensionWindowLayoutInfo(activity);
+            final Rect secondBounds = getActivityBounds(activity);
+            final Rect secondMaximumBounds = getMaximumActivityBounds(activity);
+
+            final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
+                    firstMaximumBounds, secondMaximumBounds);
+            assertEqualWindowLayoutInfo(firstWindowLayout, secondWindowLayout,
+                    firstBounds, secondBounds, doesDisplayRotateForOrientation);
+        }
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+    public void testGetWindowLayoutInfo_windowRecreated_windowLayoutUpdates()
+            throws  InterruptedException {
+        assumeSupportsRotation();
+
+        try (IgnoreOrientationRequestSession session =
+                     new IgnoreOrientationRequestSession(false /* enable */)) {
             mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
             assumeHasDisplayFeatures(mWindowLayoutInfo);
 
@@ -224,7 +384,6 @@
 
             final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
                     portraitMaximumBounds, landscapeMaximumBounds);
-            assertTrue(doesDisplayRotateForOrientation);
             assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo,
                     portraitBounds, landscapeBounds, doesDisplayRotateForOrientation);
         }
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java
index f0b5a7c..f30a7d4 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java
@@ -18,45 +18,28 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.server.wm.jetpack.utils.SidecarUtil.MINIMUM_SIDECAR_VERSION;
 import static android.server.wm.jetpack.utils.SidecarUtil.assertEqualWindowLayoutInfo;
 import static android.server.wm.jetpack.utils.SidecarUtil.assumeHasDisplayFeatures;
 import static android.server.wm.jetpack.utils.SidecarUtil.assumeSidecarSupportedDevice;
 import static android.server.wm.jetpack.utils.SidecarUtil.getSidecarInterface;
-import static android.server.wm.jetpack.utils.SidecarUtil.isSidecarVersionValid;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.assertNotBothDimensionsZero;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.assertHasNonNegativeDimensions;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.doesDisplayRotateForOrientation;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.getActivityBounds;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.getActivityWindowToken;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.getMaximumActivityBounds;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.setActivityOrientationActivityDoesNotHandleOrientationChanges;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.setActivityOrientationActivityHandlesOrientationChanges;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 
-import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
-import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
 import android.server.wm.jetpack.utils.SidecarCallbackCounter;
 import android.server.wm.jetpack.utils.TestActivity;
 import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
 import android.server.wm.jetpack.utils.TestGetWindowLayoutInfoActivity;
+import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
-
 import androidx.window.sidecar.SidecarDeviceState;
 import androidx.window.sidecar.SidecarDisplayFeature;
 import androidx.window.sidecar.SidecarInterface;
@@ -69,8 +52,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-
 /**
  * Tests for the {@link androidx.window.sidecar} implementation provided on the device (and only
  * if one is available).
@@ -78,6 +59,7 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerJetpackTestCases:SidecarTest
  */
+@Presubmit
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class SidecarTest extends WindowManagerJetpackTestBase {
@@ -192,8 +174,7 @@
 
         // The value is verified inside TestGetWindowLayoutInfoActivity
         TestGetWindowLayoutInfoActivity.resetResumeCounter();
-        TestGetWindowLayoutInfoActivity testGetWindowLayoutInfoActivity
-                = (TestGetWindowLayoutInfoActivity) startActivityNewTask(
+        TestGetWindowLayoutInfoActivity testGetWindowLayoutInfoActivity = startActivityNewTask(
                         TestGetWindowLayoutInfoActivity.class);
 
         // Make sure the activity has gone through all states.
@@ -205,8 +186,7 @@
     public void testGetWindowLayoutInfo_configChanged_windowLayoutUpdates() {
         assumeHasDisplayFeatures(mSidecarInterface, mWindowToken);
 
-        TestConfigChangeHandlingActivity configHandlingActivity
-                = (TestConfigChangeHandlingActivity) startActivityNewTask(
+        TestConfigChangeHandlingActivity configHandlingActivity = startActivityNewTask(
                 TestConfigChangeHandlingActivity.class);
         SidecarInterface sidecar = getSidecarInterface(configHandlingActivity);
         assertThat(sidecar).isNotNull();
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/WindowExtensionsImplTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/WindowExtensionsImplTest.java
new file mode 100644
index 0000000..fd14846
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/WindowExtensionsImplTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack;
+
+import static android.server.wm.jetpack.utils.ExtensionUtil.EXTENSION_VERSION_1;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionVersion;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link androidx.window.extensions.WindowExtensionsImpl} implementation.
+ * Verifies that the extensions API level is aligned or higher than the current level.
+ *
+ * Build/Install/Run:
+ * atest CtsWindowManagerJetpackTestCases:WindowExtensionsImplTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowExtensionsImplTest {
+
+    @ApiTest(apis = {"androidx.window.extensions.WindowExtensions#getVendorApiLevel"})
+    @Test
+    public void testVerifiesExtensionVendorApiLevel() {
+        assumeExtensionSupportedDevice();
+        assertTrue(getExtensionVersion().compareTo(EXTENSION_VERSION_1) >= 0);
+    }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
index aeb1cbf..84873b2 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
@@ -178,6 +179,21 @@
         return secondaryActivity;
     }
 
+    public static void startActivityAndVerifyNoCallback(@NonNull Activity activityLaunchingFrom,
+            @NonNull Class secondActivityClass, @NonNull String secondaryActivityId,
+            @NonNull TestValueCountConsumer<List<SplitInfo>> splitInfoConsumer) throws Exception {
+        // We expect the actual count to be 0. Set to 1 to trigger the timeout and verify no calls.
+        splitInfoConsumer.setCount(1);
+
+        // Start second activity
+        startActivityFromActivity(activityLaunchingFrom, secondActivityClass, secondaryActivityId);
+
+        // A split info callback should occur after the new activity is launched because the split
+        // states have changed.
+        List<SplitInfo> activeSplitStates = splitInfoConsumer.waitAndGet();
+        assertNull("Received SplitInfo value but did not expect none.", activeSplitStates);
+    }
+
     public static Activity startActivityAndVerifySplit(@NonNull Activity primaryActivity,
             @NonNull Class secondActivityClass, @NonNull SplitPairRule splitPairRule,
             @NonNull String secondActivityId, int expectedCallbackCount,
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java
index 143f7c0..245052e 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java
@@ -23,21 +23,22 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
+import android.content.Context;
 import android.graphics.Rect;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.UiContext;
 import androidx.window.extensions.WindowExtensions;
 import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.area.WindowAreaComponent;
 import androidx.window.extensions.layout.DisplayFeature;
 import androidx.window.extensions.layout.FoldingFeature;
 import androidx.window.extensions.layout.WindowLayoutComponent;
 import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
 
 /**
@@ -49,7 +50,9 @@
 
     private static final String EXTENSION_TAG = "Extension";
 
-    public static final Version MINIMUM_EXTENSION_VERSION = new Version(1, 0, 0, "");
+    public static final Version EXTENSION_VERSION_1 = new Version(1, 0, 0, "");
+
+    public static final Version EXTENSION_VERSION_2 = new Version(1, 1, 0, "");
 
     @NonNull
     public static Version getExtensionVersion() {
@@ -67,10 +70,29 @@
         return Version.UNKNOWN;
     }
 
+    public static boolean isExtensionVersionAtLeast(Version targetVersion) {
+        final Version version = getExtensionVersion();
+        return version.compareTo(targetVersion) >= 0;
+    }
+
+    /**
+     * If called on a device with the vendor api level less than the bound then the test will be
+     * ignored.
+     * @param vendorApiLevel minimum {@link WindowExtensions#getVendorApiLevel()} for a test to
+     *                       succeed
+     */
+    public static void assumeVendorApiLevelAtLeast(int vendorApiLevel) {
+        final Version version = getExtensionVersion();
+        assumeTrue(
+                "Needs vendorApiLevel " + vendorApiLevel + " but has " + version.getMajor(),
+                version.getMajor() >= vendorApiLevel
+        );
+    }
+
     public static boolean isExtensionVersionValid() {
         final Version version = getExtensionVersion();
         // Check that the extension version on the device is at least the minimum valid version.
-        return version.compareTo(MINIMUM_EXTENSION_VERSION) >= 0;
+        return version.compareTo(EXTENSION_VERSION_1) >= 0;
     }
 
     @Nullable
@@ -90,7 +112,7 @@
         assumeTrue("Device does not support extensions", extensionNotNull);
         // If extensions are on the device, make sure that the version is valid.
         assertTrue("Extension version is invalid, must be at least "
-                + MINIMUM_EXTENSION_VERSION.toString(), isExtensionVersionValid());
+                + EXTENSION_VERSION_1.toString(), isExtensionVersionValid());
     }
 
     @Nullable
@@ -102,6 +124,12 @@
         return extension.getWindowLayoutComponent();
     }
 
+    /**
+     * Publishes a WindowLayoutInfo update to a test consumer. In EXTENSION_VERSION_1, only type
+     * Activity can be the listener to WindowLayoutInfo changes. This method should be called at
+     * most once for each given Activity because addWindowLayoutInfoListener implementation
+     * assumes a 1-1 mapping between the activity and consumer.
+     */
     @Nullable
     public static WindowLayoutInfo getExtensionWindowLayoutInfo(Activity activity)
             throws InterruptedException {
@@ -122,9 +150,36 @@
         return info;
     }
 
+    /**
+     * Publishes a WindowLayoutInfo update to a test consumer. In EXTENSION_VERSION_2 both type
+     * WindowContext and Activity can be listeners. This method should be called at most once for
+     * each given Context because addWindowLayoutInfoListener implementation assumes a 1-1
+     * mapping between the context and consumer.
+     */
+    @Nullable
+    public static WindowLayoutInfo getExtensionWindowLayoutInfo(@UiContext Context context)
+            throws InterruptedException {
+        assertTrue(isExtensionVersionAtLeast(EXTENSION_VERSION_2));
+        WindowLayoutComponent windowLayoutComponent = getExtensionWindowLayoutComponent();
+        if (windowLayoutComponent == null) {
+            return null;
+        }
+        TestValueCountConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
+                new TestValueCountConsumer<>();
+        windowLayoutComponent.addWindowLayoutInfoListener(context, windowLayoutInfoConsumer);
+        WindowLayoutInfo info = windowLayoutInfoConsumer.waitAndGet();
+
+        // The default implementation only allows a single listener per context. Since we are using
+        // a local windowLayoutInfoConsumer within this function, we must remember to clean up.
+        // Otherwise, subsequent calls to addWindowLayoutInfoListener with the same context will
+        // fail to have its callback registered.
+        windowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
+        return info;
+    }
+
     @NonNull
     public static int[] getExtensionDisplayFeatureTypes(Activity activity)
-            throws ExecutionException, InterruptedException, TimeoutException {
+            throws InterruptedException {
         WindowLayoutInfo windowLayoutInfo = getExtensionWindowLayoutInfo(activity);
         if (windowLayoutInfo == null) {
             return new int[0];
@@ -267,4 +322,17 @@
                 .filter(d -> otherOrientationBounds.contains(d.getBounds()))
                 .collect(Collectors.toList());
     }
+
+    /**
+     * Returns the {@link WindowAreaComponent} available in {@link WindowExtensions} if available.
+     * If the component is not available, returns null.
+     */
+    @Nullable
+    public static WindowAreaComponent getExtensionWindowAreaComponent() {
+        WindowExtensions extension = getWindowExtensions();
+        if (extension == null || extension.getVendorApiLevel() < 2) {
+            return null;
+        }
+        return extension.getWindowAreaComponent();
+    }
 }
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityLauncher.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityLauncher.java
new file mode 100644
index 0000000..22b77ed
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityLauncher.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack.utils;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class TestActivityLauncher<T extends Activity> {
+
+    /** Key for string extra, ID to track an Activity that is launched. */
+    public static final String KEY_ACTIVITY_ID = "ActivityID";
+
+    /**
+     * Options that will be passed to the instrumentation.
+     * @see TestActivityLauncher#launch(Instrumentation)
+     */
+    private final ActivityOptions mOptions = ActivityOptions.makeBasic();
+
+    /**
+     * The class for the {@link Activity} that you are launching.
+     */
+    private final Class<T> mActivityClass;
+
+    /**
+     * The intent that will be used to launch the {@link Activity}.
+     */
+    private final Intent mIntent;
+
+    public TestActivityLauncher(@NonNull Context context, @NonNull Class<T> activityClass) {
+        mActivityClass = activityClass;
+        mIntent = new Intent(context, activityClass);
+    }
+
+    public TestActivityLauncher<T> addIntentFlag(int flag) {
+        mIntent.addFlags(flag);
+        return this;
+    }
+
+    public TestActivityLauncher<T> setActivityId(@Nullable String id) {
+        mIntent.putExtra(KEY_ACTIVITY_ID, id);
+        return this;
+    }
+
+    public TestActivityLauncher<T> setWindowingMode(int windowingMode) {
+        mOptions.setLaunchWindowingMode(windowingMode);
+        return this;
+    }
+
+    public T launch(@NonNull Instrumentation instrumentation) {
+        return mActivityClass.cast(instrumentation.startActivitySync(mIntent, mOptions.toBundle()));
+    }
+
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java
index 1987020..455db39 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java
@@ -16,7 +16,7 @@
 
 package android.server.wm.jetpack.utils;
 
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.ACTIVITY_ID_LABEL;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
 
 import android.content.Intent;
 import android.os.Bundle;
@@ -37,8 +37,8 @@
 
         // Get ID
         Intent intent = getIntent();
-        if (intent != null && intent.hasExtra(ACTIVITY_ID_LABEL)) {
-            mId = intent.getStringExtra(ACTIVITY_ID_LABEL);
+        if (intent != null && intent.hasExtra(KEY_ACTIVITY_ID)) {
+            mId = intent.getStringExtra(KEY_ACTIVITY_ID);
         }
     }
 
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestRearDisplayActivity.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestRearDisplayActivity.java
new file mode 100644
index 0000000..5e19aec6
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestRearDisplayActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack.utils;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+public class TestRearDisplayActivity extends Activity {
+
+    public boolean mConfigurationChanged;
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mConfigurationChanged = true;
+    }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java
index 10fe6fc..f221875 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java
@@ -62,6 +62,11 @@
         return value;
     }
 
+    // Doesn't change the count.
+    public void clearQueue() {
+        mLinkedBlockingQueue.clear();
+    }
+
     @Nullable
     public T getLastReportedValue() {
         return mLastReportedValue;
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java
index f8be26c..b6e2919 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java
@@ -81,7 +81,8 @@
         return matcher.matches();
     }
 
-    int getMajor() {
+    /** Major version of the vendor implementation. */
+    public int getMajor() {
         return mMajor;
     }
 
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowExtensionTestRule.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowExtensionTestRule.java
new file mode 100644
index 0000000..5ae4b72
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowExtensionTestRule.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack.utils;
+
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowAreaComponent;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutComponent;
+
+import static org.junit.Assume.assumeNotNull;
+
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class WindowExtensionTestRule implements TestRule {
+
+    private final Class<?> mComponentToFetch;
+    private Object mComponent;
+
+    public Object getExtensionComponent() {
+        return mComponent;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                assumeExtensionSupportedDevice();
+                mComponent = getComponent(mComponentToFetch);
+                assumeNotNull(mComponent);
+                base.evaluate();
+            }
+        };
+    }
+
+    public WindowExtensionTestRule(Class<?> componentType) {
+        mComponentToFetch = componentType;
+    }
+
+    public Object getComponent(Class<?> componentToFetch) {
+        if (componentToFetch == WindowAreaComponent.class) {
+            return getExtensionWindowAreaComponent();
+        } else if (componentToFetch == WindowLayoutComponent.class) {
+            return getExtensionWindowLayoutComponent();
+        }
+        return null;
+    }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
index 2c8c843..0495238 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
@@ -16,12 +16,17 @@
 
 package android.server.wm.jetpack.utils;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
+import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -30,11 +35,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.Application;
 import android.app.Instrumentation;
+import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -57,8 +64,8 @@
 /** Base class for all tests in the module. */
 public class WindowManagerJetpackTestBase {
 
-    public static final String ACTIVITY_ID_LABEL = "ActivityID";
     public static final String EXTRA_EMBED_ACTIVITY = "EmbedActivity";
+    public static final String EXTRA_SPLIT_RATIO = "SplitRatio";
 
     public Instrumentation mInstrumentation;
     public Context mContext;
@@ -85,24 +92,45 @@
         sVisibleActivities.clear();
     }
 
-    public Activity startActivityNewTask(@NonNull Class activityClass) {
+    protected boolean hasDeviceFeature(final String requiredFeature) {
+        return mContext.getPackageManager().hasSystemFeature(requiredFeature);
+    }
+
+    /**
+     * Rotation support is indicated by explicitly having both landscape and portrait
+     * features or not listing either at all.
+     */
+    protected void assumeSupportsRotation() {
+        final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
+        final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
+        assumeTrue((supportsLandscape && supportsPortrait)
+                || (!supportsLandscape && !supportsPortrait));
+    }
+
+    public <T extends Activity> T startActivityNewTask(@NonNull Class<T> activityClass) {
         return startActivityNewTask(activityClass, null /* activityId */);
     }
 
-    public Activity startActivityNewTask(@NonNull Class activityClass,
+    public <T extends Activity> T startActivityNewTask(@NonNull Class<T> activityClass,
             @Nullable String activityId) {
-        return startActivityNewTask(mContext, mInstrumentation, activityClass, activityId);
+        return launcherForActivityNewTask(activityClass, activityId, false /* isFullScreen */)
+                .launch(mInstrumentation);
     }
 
-    public static Activity startActivityNewTask(@NonNull Context context,
-            @NonNull Instrumentation instrumentation, @NonNull Class activityClass,
+    public <T extends  Activity> T startFullScreenActivityNewTask(@NonNull Class<T> activityClass,
             @Nullable String activityId) {
-        final Intent intent = new Intent(context, activityClass);
-        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        if (activityId != null) {
-            intent.putExtra(ACTIVITY_ID_LABEL, activityId);
-        }
-        return instrumentation.startActivitySync(intent);
+        return launcherForActivityNewTask(activityClass, activityId, true/* isFullScreen */)
+                .launch(mInstrumentation);
+    }
+
+    private <T extends Activity> TestActivityLauncher<T> launcherForActivityNewTask(
+            @NonNull Class<T> activityClass, @Nullable String activityId, boolean isFullScreen) {
+        final int windowingMode = isFullScreen ? WINDOWING_MODE_FULLSCREEN :
+                WINDOWING_MODE_UNDEFINED;
+        return new TestActivityLauncher<>(mContext, activityClass)
+                .addIntentFlag(FLAG_ACTIVITY_NEW_TASK)
+                .setActivityId(activityId)
+                .setWindowingMode(windowingMode);
     }
 
     /**
@@ -140,7 +168,7 @@
     public static <T extends Activity> void startActivityFromActivity(Activity activityToLaunchFrom,
             Class<T> activityToLaunchClass, String newActivityId) {
         Intent intent = new Intent(activityToLaunchFrom, activityToLaunchClass);
-        intent.putExtra(ACTIVITY_ID_LABEL, newActivityId);
+        intent.putExtra(KEY_ACTIVITY_ID, newActivityId);
         activityToLaunchFrom.startActivity(intent);
     }
 
@@ -153,7 +181,7 @@
         Intent intent = new Intent();
         intent.setClassName(activityToLaunchComponent.getPackageName(),
                 activityToLaunchComponent.getClassName());
-        intent.putExtra(ACTIVITY_ID_LABEL, newActivityId);
+        intent.putExtra(KEY_ACTIVITY_ID, newActivityId);
         intent.putExtras(extras);
         activityToLaunchFrom.startActivity(intent);
     }
@@ -209,6 +237,36 @@
         assertEquals(orientation, activity.getResources().getConfiguration().orientation);
     }
 
+    public static void enterPipActivityHandlesConfigChanges(TestActivity activity) {
+        if (activity.isInPictureInPictureMode()) {
+            throw new IllegalStateException("Activity must not be in PiP");
+        }
+        activity.resetLayoutCounter();
+        // Enter picture in picture
+        PictureInPictureParams params = (new PictureInPictureParams.Builder()).build();
+        activity.enterPictureInPictureMode(params);
+        // Wait for the activity to layout, which will happen after the Activity has been resized.
+        assertTrue(activity.waitForLayout());
+        // Check that Activity is in PiP.
+        assertTrue(activity.isInPictureInPictureMode());
+    }
+
+    public static void exitPipActivityHandlesConfigChanges(TestActivity activity) {
+        if (!activity.isInPictureInPictureMode()) {
+            throw new IllegalStateException("Activity must be in PiP");
+        }
+        activity.resetLayoutCounter();
+        // Launch the same Activity using the single top flag so that the PiP Activity will be
+        // expanded to full screen.
+        Intent intent = new Intent(activity, activity.getClass());
+        intent.addFlags(FLAG_ACTIVITY_SINGLE_TOP);
+        activity.startActivity(intent);
+        // Wait for the activity to layout, which will happen after the Activity has been resized.
+        assertTrue(activity.waitForLayout());
+        // Check that the Activity is not in PiP.
+        assertFalse(activity.isInPictureInPictureMode());
+    }
+
     public static void setActivityOrientationActivityDoesNotHandleOrientationChanges(
             TestActivity activity, int orientation) {
         // Make sure that the provided orientation is a fixed orientation
@@ -234,6 +292,10 @@
      * display rotates for orientation, then the maximum portrait bounds will be a rotated version
      * of the maximum landscape bounds.
      */
+    // TODO(b/186631239): ActivityManagerTestBase#ignoresOrientationRequests could disable
+    // activity rotation, as a result the display area would remain in the old orientation while
+    // the activity orientation changes. We should check the existence of this request before
+    // running tests that compare orientation values.
     public static boolean doesDisplayRotateForOrientation(@NonNull Rect portraitMaximumBounds,
             @NonNull Rect landscapeMaximumBounds) {
         return !portraitMaximumBounds.equals(landscapeMaximumBounds);
diff --git a/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar b/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
index 367e3b9..7eee6da 100644
--- a/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
+++ b/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
Binary files differ
diff --git a/tests/framework/base/windowmanager/res/values/styles.xml b/tests/framework/base/windowmanager/res/values/styles.xml
index 2945940..d9f0c15 100644
--- a/tests/framework/base/windowmanager/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/res/values/styles.xml
@@ -57,4 +57,10 @@
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
     </style>
-</resources>
+    <style name="window_task_animation" parent="@android:style/Theme.DeviceDefault">
+      <item name="android:taskOpenEnterAnimation">@anim/alpha</item>
+      <item name="android:taskOpenExitAnimation">@anim/alpha</item>
+      <item name="android:taskCloseEnterAnimation">@anim/alpha</item>
+      <item name="android:taskCloseExitAnimation">@anim/alpha</item>
+    </style>
+ </resources>
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
index d47cc6d..073e584 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -142,7 +142,7 @@
         waitAndAssertTopResumedActivity(new ComponentName(mContext, TransitionActivity.class),
                 DEFAULT_DISPLAY, "Activity must be launched");
 
-        latch.await(3, TimeUnit.SECONDS);
+        latch.await(5, TimeUnit.SECONDS);
         final long totalTime = transitionEndTime.get() - transitionStartTime.get();
         assertTrue("Actual transition duration should be in the range "
                 + "<" + CUSTOM_ANIMATION_DURATION_RANGE.getLower() + ", "
@@ -182,6 +182,41 @@
                 + "actual=" + totalTime, !CUSTOM_ANIMATION_DURATION_RANGE.contains(totalTime));
     }
 
+    @Test
+    public void testTaskWindowAnimationOverrideDisabled() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        AtomicLong transitionStartTime = new AtomicLong();
+        AtomicLong transitionEndTime = new AtomicLong();
+
+        final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+            transitionEndTime.set(t);
+            latch.countDown();
+        };
+
+        // Overriding task transit animation is disabled, so default wallpaper close animation
+        // is played.
+        final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.alpha, 0 /* exitResId */, 0 /* backgroundColor */,
+                new Handler(Looper.getMainLooper()), startedListener, finishedListener).toBundle();
+
+        final ComponentName customWindowAnimationActivity = new ComponentName(
+                mContext, CustomWindowAnimationActivity.class);
+        final Intent intent = new Intent().setComponent(customWindowAnimationActivity)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent, bundle);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        waitAndAssertTopResumedActivity(customWindowAnimationActivity, DEFAULT_DISPLAY,
+                "Activity must be launched");
+
+        latch.await(5, TimeUnit.SECONDS);
+        final long totalTime = transitionEndTime.get() - transitionStartTime.get();
+        assertTrue("Actual transition duration should be out of the range "
+                + "<" + CUSTOM_ANIMATION_DURATION_RANGE.getLower() + ", "
+                + CUSTOM_ANIMATION_DURATION_RANGE.getUpper() + "> ms, "
+                + "actual=" + totalTime, !CUSTOM_ANIMATION_DURATION_RANGE.contains(totalTime));
+    }
+
     /**
      * Checks that the activity's theme's background color is used as the default animation's
      * background color when no override is specified.
@@ -734,7 +769,7 @@
                 Bundle extras) {
             final Intent i = new Intent(this, klass);
             i.putExtras(extras);
-            startActivity(i, activityOptions.toBundle());
+            startActivity(i, activityOptions != null ? activityOptions.toBundle() : null);
         }
     }
 
@@ -804,4 +839,6 @@
             overridePendingTransition(enterAnim, R.anim.alpha_0);
         }
     }
+
+    public static class CustomWindowAnimationActivity extends Activity { }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 9762bc4..eda914f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -198,8 +198,7 @@
         assumeTrue(supportsLockScreen());
 
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
-        final boolean notSupportsInsecureLock = !supportsInsecureLock();
-        if (notSupportsInsecureLock) {
+        if (!supportsInsecureLock()) {
             lockScreenSession.setLockCredential();
         }
         final ActivitySessionClient activityClient = createManagedActivityClientSession();
@@ -207,11 +206,25 @@
                 true /* useWindowFlags */);
         testTurnScreenOnActivity(lockScreenSession, activityClient,
                 false /* useWindowFlags */);
-        if (notSupportsInsecureLock) {
-            // In the platform without InsecureLock, we just test if the display is on with
-            // TurnScreenOnActivity.
-            mObjectTracker.close(lockScreenSession);
-        }
+
+        // Start TURN_SCREEN_ON_ACTIVITY
+        launchActivity(TURN_SCREEN_ON_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+        assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+
+        // Start another activity on top and put device to sleep
+        final ActivitySession activity = activityClient.startActivity(
+                getLaunchActivityBuilder().setUseInstrumentation()
+                        .setWaitForLaunched(false).setTargetActivity(TOP_ACTIVITY));
+        waitAndAssertActivityState(TOP_ACTIVITY, STATE_STOPPED, "Activity must be stopped.");
+        lockScreenSession.sleepDevice();
+
+        // Finish the top activity and make sure the device still in sleep
+        activity.finish();
+        waitAndAssertActivityState(TURN_SCREEN_ON_ACTIVITY, STATE_STOPPED,
+                "Activity must be stopped");
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, false);
+        assertFalse("Display must be remained OFF", isDisplayOn(DEFAULT_DISPLAY));
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
index 02457a6..4725b45 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
@@ -518,9 +518,6 @@
      */
     private void runSizeCompatTest(ComponentName activity, int windowingMode, double resizeRatio,
             boolean inSizeCompatModeAfterResize) {
-        // TODO(b/208918131): Remove once real cause is found.
-        assumeFalse(ENABLE_SHELL_TRANSITIONS);
-
         launchActivity(activity, windowingMode);
 
         assertSizeCompatMode(activity, /* expectedInSizeCompatMode= */ false);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
index b3f2804..4a3503d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
@@ -83,7 +83,7 @@
         startFullscreenTestActivity();
         mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);
 
-        mDreamCoordinator.startDream(TEST_DREAM_SERVICE);
+        mDreamCoordinator.startDream();
         waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
                 "Dream activity should be the top resumed activity");
         mWmState.waitForValidState(mWmState.getHomeActivityName());
@@ -104,7 +104,7 @@
     public void testDreamServiceStopsTimely() throws Exception {
         mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);
 
-        mDreamCoordinator.startDream(TEST_DREAM_SERVICE);
+        mDreamCoordinator.startDream();
         waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
                 "Dream activity should be the top resumed activity");
         mWmState.waitForValidState(mWmState.getHomeActivityName());
@@ -124,7 +124,7 @@
         startFullscreenTestActivity();
         mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_STUBBORN_DREAM_SERVICE);
 
-        mDreamCoordinator.startDream(TEST_STUBBORN_DREAM_SERVICE);
+        mDreamCoordinator.startDream();
         waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
                 "Dream activity should be the top resumed activity");
         mWmState.waitForValidState(mWmState.getHomeActivityName());
@@ -149,7 +149,7 @@
         final RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(Surface.ROTATION_0);
         mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);
-        mDreamCoordinator.startDream(TEST_DREAM_SERVICE);
+        mDreamCoordinator.startDream();
         rotationSession.set(Surface.ROTATION_90);
 
         waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
@@ -243,7 +243,7 @@
     private class DreamingState implements AutoCloseable {
         public DreamingState(ComponentName dream) {
             mDreamActivityName = mDreamCoordinator.setActiveDream(dream);
-            mDreamCoordinator.startDream(dream);
+            mDreamCoordinator.startDream();
             waitAndAssertDreaming();
         }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeepScreenOnTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeepScreenOnTests.java
new file mode 100644
index 0000000..a97399e
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeepScreenOnTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TURN_SCREEN_ON_ACTIVITY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class KeepScreenOnTests extends MultiDisplayTestBase {
+    private static final String TAG = "KeepScreenOnTests";
+    private String mInitialDisplayTimeout;
+    private PowerManager mPowerManager;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mInitialDisplayTimeout =
+                Settings.System.getString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+    }
+
+    @After
+    public void tearDown() {
+        setScreenOffTimeoutMs(mInitialDisplayTimeout);
+    }
+
+    @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+    @Test
+    public void testKeepScreenOn_activityOnDefaultDisplay_screenStaysOn() {
+        setScreenOffTimeoutMs("500");
+        launchActivity(TURN_SCREEN_ON_ACTIVITY);
+        assertTrue(mPowerManager.isInteractive());
+
+        SystemClock.sleep(getMinimumScreenOffTimeoutMs());
+
+        assertTrue(mPowerManager.isInteractive());
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+    }
+
+    @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+    @Test
+    public void testKeepScreenOn_activityNotForeground_screenTurnsOff() {
+        setScreenOffTimeoutMs("500");
+
+        launchActivity(TURN_SCREEN_ON_ACTIVITY);
+        assertTrue(mPowerManager.isInteractive());
+        try (BlockingBroadcastReceiver r = BlockingBroadcastReceiver.create(mContext,
+                Intent.ACTION_SCREEN_OFF).register()) {
+            launchActivity(TEST_ACTIVITY);
+        }
+        mWmState.waitAndAssertVisibilityGone(TURN_SCREEN_ON_ACTIVITY);
+        assertFalse(mPowerManager.isInteractive());
+    }
+
+    @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+    @Test
+    public void testKeepScreenOn_activityOnVirtualDisplay_screenStaysOn() {
+        assumeTrue(supportsMultiDisplay());
+        setScreenOffTimeoutMs("500");
+
+        final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true).createDisplay();
+        launchActivityOnDisplay(TURN_SCREEN_ON_ACTIVITY, newDisplay.mId);
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+        assertTrue(mPowerManager.isInteractive());
+
+        SystemClock.sleep(getMinimumScreenOffTimeoutMs());
+
+        assertTrue(mPowerManager.isInteractive());
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+    }
+
+    @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+    @Test
+    public void testKeepScreenOn_activityOnVirtualDisplayNotForeground_screenTurnsOff() {
+        assumeTrue(supportsMultiDisplay());
+        setScreenOffTimeoutMs("500");
+
+        final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true).createDisplay();
+        launchActivityOnDisplay(TURN_SCREEN_ON_ACTIVITY, newDisplay.mId);
+        assertTrue(mPowerManager.isInteractive());
+        try (BlockingBroadcastReceiver r = BlockingBroadcastReceiver.create(mContext,
+                Intent.ACTION_SCREEN_OFF).register()) {
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+        }
+        mWmState.waitAndAssertVisibilityGone(TURN_SCREEN_ON_ACTIVITY);
+        assertFalse(mPowerManager.isInteractive());
+    }
+
+    private void setScreenOffTimeoutMs(String timeoutMs) {
+        Settings.System.putString(
+                mContext.getContentResolver(), SCREEN_OFF_TIMEOUT, timeoutMs);
+    }
+
+    private int getMinimumScreenOffTimeoutMs() {
+        return mContext.getResources().getInteger(
+                Resources.getSystem().getIdentifier("config_minimumScreenOffTimeout", "integer",
+                        "android"));
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index d425363..47845f5 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -45,9 +45,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.never;
@@ -62,7 +60,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.ActivitySessionClient;
-import android.server.wm.WindowManagerState.WindowState;
 import android.server.wm.app.Components;
 
 import androidx.test.filters.FlakyTest;
@@ -196,16 +193,13 @@
      */
     @Test
     public void testTranslucentShowWhenLockedActivity() {
-        // TODO(b/209906849) remove assumeFalse after issue fix.
-        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
         mWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
         mWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
         lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
-        mWmState.computeState();
         mWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
-        assertWallpaperShowing();
+        mWmState.waitAndAssertWindowShown(TYPE_WALLPAPER, true);
         mWmState.assertKeyguardShowingAndOccluded();
     }
 
@@ -230,16 +224,13 @@
 
     @Test
     public void testDialogShowWhenLockedActivity() {
-        // TODO(b/209906849) remove assumeFalse after issue fix.
-        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         launchActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
         mWmState.computeState(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
         mWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
-        lockScreenSession.gotoKeyguard();
-        mWmState.computeState();
+        lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
         mWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
-        assertWallpaperShowing();
+        mWmState.waitAndAssertWindowShown(TYPE_WALLPAPER, true);
         mWmState.assertKeyguardShowingAndOccluded();
     }
 
@@ -622,13 +613,6 @@
         mWmState.waitAndAssertActivityRemoved(SHOW_WHEN_LOCKED_ACTIVITY);
     }
 
-    private void assertWallpaperShowing() {
-        WindowState wallpaper =
-                mWmState.findFirstWindowWithType(TYPE_WALLPAPER);
-        assertNotNull(wallpaper);
-        assertTrue(wallpaper.isSurfaceShown());
-    }
-
     @Test
     public void testDismissKeyguardAttrActivity_method_turnScreenOn() {
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
index ab08a11..0dd0406 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
@@ -36,6 +36,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.filters.FlakyTest;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -105,6 +107,7 @@
                 mWmState.getDefaultDisplayLastTransition());
     }
 
+    @FlakyTest(bugId = 238776938)
     @Test
     public void testDismissKeyguard() {
         createManagedLockScreenSession().gotoKeyguard();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index 75aa696..eea2396 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -171,74 +171,6 @@
         return result;
     }
 
-    public static class DisplayMetricsSession implements AutoCloseable {
-        private final ReportedDisplayMetrics mInitialDisplayMetrics;
-        private final int mDisplayId;
-
-        DisplayMetricsSession(int displayId) {
-            mDisplayId = displayId;
-            mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
-        }
-
-        ReportedDisplayMetrics getInitialDisplayMetrics() {
-            return mInitialDisplayMetrics;
-        }
-
-        ReportedDisplayMetrics getDisplayMetrics() {
-            return ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
-        }
-
-        void changeAspectRatio(double aspectRatio, int orientation) {
-            final Size originalSize = mInitialDisplayMetrics.physicalSize;
-            final int smaller = Math.min(originalSize.getWidth(), originalSize.getHeight());
-            final int larger = (int) (smaller * aspectRatio);
-            Size overrideSize;
-            if (orientation == ORIENTATION_LANDSCAPE) {
-                overrideSize = new Size(larger, smaller);
-            }
-            else {
-                overrideSize = new Size(smaller, larger);
-            }
-            overrideDisplayMetrics(overrideSize, mInitialDisplayMetrics.physicalDensity);
-        }
-
-        void changeDisplayMetrics(double sizeRatio, double densityRatio) {
-            // Given a display may already have an override applied before the test is begun,
-            // resize based upon the override.
-            final Size originalSize;
-            final int density;
-            if (mInitialDisplayMetrics.overrideSize != null) {
-                originalSize = mInitialDisplayMetrics.overrideSize;
-            } else {
-                originalSize = mInitialDisplayMetrics.physicalSize;
-            }
-
-            if (mInitialDisplayMetrics.overrideDensity != null) {
-                density = mInitialDisplayMetrics.overrideDensity;
-            } else {
-                density = mInitialDisplayMetrics.physicalDensity;
-            }
-
-            final Size overrideSize = new Size((int)(originalSize.getWidth() * sizeRatio),
-                    (int)(originalSize.getHeight() * sizeRatio));
-            final int overrideDensity = (int)(density * densityRatio);
-            overrideDisplayMetrics(overrideSize, overrideDensity);
-        }
-
-        void overrideDisplayMetrics(final Size size, final int density) {
-            mInitialDisplayMetrics.setDisplayMetrics(size, density);
-        }
-
-        void restoreDisplayMetrics() {
-            mInitialDisplayMetrics.restoreDisplayMetrics();
-        }
-
-        @Override
-        public void close() {
-            restoreDisplayMetrics();
-        }
-    }
-
     /** @see ObjectTracker#manage(AutoCloseable) */
     protected DisplayMetricsSession createManagedDisplayMetricsSession(int displayId) {
         return mObjectTracker.manage(new DisplayMetricsSession(displayId));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index cd1fcda..5171898 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -29,6 +29,7 @@
 import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.ComponentNameUtils.getWindowName;
+import static android.server.wm.UiDeviceUtils.pressBackButton;
 import static android.server.wm.UiDeviceUtils.pressWindowButton;
 import static android.server.wm.WindowManagerState.STATE_PAUSED;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
@@ -60,6 +61,7 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_BACK_PRESSED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
@@ -328,6 +330,34 @@
     }
 
     @Test
+    public void testEnterPipOnBackPressed() {
+        // Launch a PiP activity that calls enterPictureInPictureMode when it receives
+        // onBackPressed callback.
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_BACK_PRESSED, "true"));
+
+        assertEnterPipOnBackPressed(PIP_ACTIVITY);
+    }
+
+    @Test
+    public void testEnterPipOnBackPressedWithAutoPipEnabled() {
+        // Launch the PIP activity that calls enterPictureInPictureMode when it receives
+        // onBackPressed callback and set its pip params to allow auto-pip.
+        launchActivity(PIP_ACTIVITY,
+                extraString(EXTRA_ALLOW_AUTO_PIP, "true"),
+                extraString(EXTRA_ENTER_PIP_ON_BACK_PRESSED, "true"));
+
+        assertEnterPipOnBackPressed(PIP_ACTIVITY);
+    }
+
+    private void assertEnterPipOnBackPressed(ComponentName componentName) {
+        // Press the back button.
+        pressBackButton();
+        // Assert that we have entered PiP.
+        waitForEnterPipAnimationComplete(componentName);
+        assertPinnedStackExists();
+    }
+
+    @Test
     public void testEnterExpandedPipAspectRatio() {
         assumeTrue(supportsExpandedPip());
         launchActivity(PIP_ACTIVITY,
@@ -565,7 +595,7 @@
     public void testDisallowPipLaunchFromStoppedActivity() {
         // Launch the bottom pip activity which will launch a new activity on top and attempt to
         // enter pip when it is stopped
-        launchActivity(PIP_ON_STOP_ACTIVITY);
+        launchActivityNoWait(PIP_ON_STOP_ACTIVITY);
 
         // Wait for the bottom pip activity to be stopped
         mWmState.waitForActivityState(PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
@@ -694,7 +724,7 @@
         // Launch the PIP activity on pause, and have it start another activity on
         // top of itself.  Wait for the new activity to be visible and ensure that the pinned stack
         // was not created in the process
-        launchActivity(PIP_ACTIVITY,
+        launchActivityNoWait(PIP_ACTIVITY,
                 extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
                 extraString(EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY)));
         mWmState.computeState(
@@ -714,9 +744,11 @@
         // Launch the PIP activity on pause, and set it to finish itself after
         // some period.  Wait for the previous activity to be visible, and ensure that the pinned
         // stack was not created in the process
-        launchActivity(PIP_ACTIVITY,
+        launchActivityNoWait(PIP_ACTIVITY,
                 extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
+        mWmState.computeState(
+                new WaitForValidActivityState(TEST_ACTIVITY));
         assertPinnedStackDoesNotExist();
     }
 
@@ -1016,7 +1048,7 @@
      */
     @Test
     public void testPipFromTaskWithAnotherFinishingActivity() {
-        launchActivity(LAUNCH_ENTER_PIP_ACTIVITY,
+        launchActivityNoWait(LAUNCH_ENTER_PIP_ACTIVITY,
                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
 
         waitForEnterPip(PIP_ACTIVITY);
@@ -1113,8 +1145,7 @@
                 waitForOrFail("Task in lock mode", () -> {
                     return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
                 });
-                mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-                waitForEnterPip(PIP_ACTIVITY);
+                mBroadcastActionTrigger.enterPipAndWait();
                 assertPinnedStackDoesNotExist();
                 launchHomeActivityNoWait();
                 mWmState.computeState();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
index 4b7242e..7b75861b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.SplitActivityLifecycleTest.ActivityB.EXTRA_SHOW_WHEN_LOCKED;
@@ -494,6 +495,39 @@
     }
 
     /**
+     * Verifies the Activity in primary TaskFragment is no longer focused after clear adjacent
+     * TaskFragments.
+     */
+    @Test
+    public void testResetFocusedAppAfterClearAdjacentTaskFragment() {
+        // TODO(b/232476698) Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+
+        // Initialize test environment by launching Activity A and B side-by-side.
+        initializeSplitActivities(false /* verifyEmbeddedTask */);
+
+        // Request the focus on the primary TaskFragment
+        WindowContainerTransaction wct = new WindowContainerTransaction()
+                .requestFocusOnTaskFragment(mTaskFragA.getTaskFragToken());
+        mTaskFragmentOrganizer.applyTransaction(wct);
+        waitForActivityFocused(5000, mActivityA);
+        assertThat(mWmState.getFocusedApp()).isEqualTo(mActivityA.flattenToShortString());
+
+        // Expand top TaskFragment and clear the adjacent TaskFragments to have the two
+        // TaskFragment stacked.
+        wct = new WindowContainerTransaction()
+                .setBounds(mTaskFragB.getToken(), new Rect())
+                .setWindowingMode(mTaskFragB.getToken(), WINDOWING_MODE_UNDEFINED)
+                .setAdjacentTaskFragments(mTaskFragA.getTaskFragToken(), null, null);
+        mTaskFragmentOrganizer.applyTransaction(wct);
+
+        // Ensure the Activity on primary TaskFragment is stopped and no longer focused.
+        waitAndAssertActivityState(mActivityA, STATE_STOPPED, "Activity A must be stopped");
+        assertThat(mWmState.getFocusedApp()).isNotEqualTo(mActivityA.flattenToShortString());
+        assertThat(mWmState.getFocusedWindow()).isEqualTo(mActivityB.flattenToShortString());
+    }
+
+    /**
      * Verifies an Activity below adjacent translucent TaskFragments is visible.
      */
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java
index d49948f..4484568 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java
@@ -16,10 +16,13 @@
 
 package android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.TaskFragmentOrganizerTestBase.assertEmptyTaskFragment;
+import static android.server.wm.TaskFragmentOrganizerTestBase.assumeExtensionVersionAtLeast2;
 import static android.server.wm.TaskFragmentOrganizerTestBase.getActivityToken;
+import static android.server.wm.TaskFragmentOrganizerTestBase.startNewActivity;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app30.Components.SDK_30_TEST_ACTIVITY;
@@ -30,9 +33,9 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 
 import android.app.Activity;
-import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Rect;
@@ -50,9 +53,10 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ApiTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -70,8 +74,10 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class TaskFragmentOrganizerPolicyTest extends ActivityManagerTestBase {
+
     private TaskOrganizer mTaskOrganizer;
     private BasicTaskFragmentOrganizer mTaskFragmentOrganizer;
+    private final ArrayList<BasicTaskFragmentOrganizer> mOrganizers = new ArrayList<>();
 
     @Before
     @Override
@@ -79,102 +85,592 @@
         super.setUp();
         mTaskFragmentOrganizer = new BasicTaskFragmentOrganizer();
         mTaskFragmentOrganizer.registerOrganizer();
+        mOrganizers.add(mTaskFragmentOrganizer);
     }
 
     @After
     public void tearDown() {
-        if (mTaskFragmentOrganizer != null) {
-            mTaskFragmentOrganizer.unregisterOrganizer();
+        for (TaskFragmentOrganizer organizer : mOrganizers) {
+            organizer.unregisterOrganizer();
+        }
+        mOrganizers.clear();
+        if (mTaskOrganizer != null) {
+            NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer());
         }
     }
 
     /**
-     * Verifies whether performing non-TaskFragment
-     * {@link android.window.WindowContainerTransaction.HierarchyOp operations} on
-     * {@link TaskFragmentOrganizer} without permission throws {@link SecurityException}.
+     * Verifies that performing {@link WindowContainerTransaction#createTaskFragment} will fail if
+     * the fragment token is not unique.
      */
-    @Test(expected = SecurityException.class)
-    public void testPerformNonTaskFragmentHierarchyOperation_ThrowException() {
-        final List<TaskAppearedInfo> taskInfos = new ArrayList<>();
-        try {
-            // Register TaskOrganizer to obtain Task information.
-            NestedShellPermission.run(() -> {
-                mTaskOrganizer = new TaskOrganizer();
-                taskInfos.addAll(mTaskOrganizer.registerOrganizer());
-            });
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#createTaskFragment"})
+    public void testCreateTaskFragment_duplicatedFragmentToken_reportError() {
+        // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder existingFragmentToken = taskFragmentInfo.getFragmentToken();
+        final IBinder errorCallbackToken = new Binder();
 
-            // It is expected to throw Security exception when TaskFragmentOrganizer performs a
-            // non-TaskFragment hierarchy operation.
-            final WindowContainerToken taskToken = taskInfos.get(0).getTaskInfo().getToken();
-            final WindowContainerTransaction wct = new WindowContainerTransaction()
-                    .reorder(taskToken, true /* opTop */);
-            mTaskFragmentOrganizer.applyTransaction(wct);
-        } finally {
-            if (mTaskOrganizer != null) {
-                NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer());
-            }
-        }
+        // Request to create another TaskFragment using the existing fragment token.
+        final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(
+                existingFragmentToken, getActivityToken(activity), new Rect(),
+                WINDOWING_MODE_UNDEFINED);
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setErrorCallbackToken(errorCallbackToken)
+                .createTaskFragment(params);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+        mTaskFragmentOrganizer.waitForTaskFragmentError();
+
+        assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(
+                IllegalArgumentException.class);
+        assertThat(mTaskFragmentOrganizer.getErrorCallbackToken()).isEqualTo(errorCallbackToken);
     }
 
     /**
-     * Verifies whether changing property on non-TaskFragment window container without permission
-     * throws {@link SecurityException}.
+     * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on
+     * non-TaskFragment window will throw {@link SecurityException}.
      */
-    @Test(expected = SecurityException.class)
-    public void testSetPropertyOnNonTaskFragment_ThrowException() {
-        final List<TaskAppearedInfo> taskInfos = new ArrayList<>();
-        try {
-            // Register TaskOrganizer to obtain Task information.
-            NestedShellPermission.run(() -> {
-                mTaskOrganizer = new TaskOrganizer();
-                taskInfos.addAll(mTaskOrganizer.registerOrganizer());
-            });
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#deleteTaskFragment"})
+    public void testDeleteTaskFragment_nonTaskFragmentWindow_throwException() {
+        final WindowContainerToken taskToken = getFirstTaskToken();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .deleteTaskFragment(taskToken);
 
-            // It is expected to throw SecurityException when TaskFragmentOrganizer attempts to
-            // change the property on non-TaskFragment container.
-            final WindowContainerToken taskToken = taskInfos.get(0).getTaskInfo().getToken();
-            final WindowContainerTransaction wct = new WindowContainerTransaction()
-                    .setBounds(taskToken, new Rect());
-            mTaskFragmentOrganizer.applyTransaction(wct);
-        } finally {
-            if (mTaskOrganizer != null) {
-                NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer());
-            }
-        }
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
     }
 
     /**
-     * Verifies whether performing TaskFragment
-     * {@link android.window.WindowContainerTransaction.HierarchyOp operations} on the TaskFragment
-     * which is not organized by given {@link TaskFragmentOrganizer} throws
+     * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on
+     * non-organized TaskFragment will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#deleteTaskFragment"})
+    public void testDeleteTaskFragment_nonOrganizedTaskFragment_throwException() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .deleteTaskFragment(taskFragmentToken);
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on organized
+     * TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#deleteTaskFragment"})
+    public void testDeleteTaskFragment_organizedTaskFragment() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .deleteTaskFragment(taskFragmentToken);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#setAdjacentRoots} on
+     * non-TaskFragment window will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setAdjacentRoots"})
+    public void testSetAdjacentRoots_nonTaskFragmentWindow_throwException() {
+        // TODO(b/232476698) The TestApi is changed. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final WindowContainerToken taskToken = getFirstTaskToken();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setAdjacentRoots(taskToken, taskToken);
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#setAdjacentRoots} on
+     * non-organized TaskFragment will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setAdjacentRoots"})
+    public void testSetAdjacentRoots_nonOrganizedTaskFragment_throwException() {
+        // TODO(b/232476698) The TestApi is changed. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken0 = taskFragmentInfo0.getToken();
+        final WindowContainerToken taskFragmentToken1 = taskFragmentInfo1.getToken();
+
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setAdjacentRoots(taskFragmentToken0, taskFragmentToken1);
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#setAdjacentRoots} on organized
+     * TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setAdjacentRoots"})
+    public void testSetAdjacentRoots_organizedTaskFragment() {
+        // TODO(b/232476698) The TestApi is changed. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken0 = taskFragmentInfo0.getToken();
+        final WindowContainerToken taskFragmentToken1 = taskFragmentInfo1.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setAdjacentRoots(taskFragmentToken0, taskFragmentToken1);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#reparentChildren} on
+     * non-TaskFragment window will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#reparentChildren"})
+    public void testReparentChildren_nonTaskFragmentWindow_throwException() {
+        final WindowContainerToken taskToken = getFirstTaskToken();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .reparentChildren(taskToken, null /* newParent */);
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#reparentChildren} on
+     * non-organized TaskFragment will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#reparentChildren"})
+    public void testReparentChildren_nonOrganizedTaskFragment_throwException() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .reparentChildren(taskFragmentToken, null /* newParent */);
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#reparentChildren} on organized
+     * TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#reparentChildren"})
+    public void testReparent_organizedTaskFragment() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .reparentChildren(taskFragmentToken, null /* newParent */);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#startActivityInTaskFragment} on
+     * non-organized TaskFragment will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#startActivityInTaskFragment"})
+    public void testStartActivityInTaskFragment_nonOrganizedTaskFragment_throwException() {
+        // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+        final IBinder callerToken = getActivityToken(activity);
+        final Intent intent = new Intent(mContext, TestActivity.class);
+
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .startActivityInTaskFragment(fragmentToken, callerToken, intent,
+                        null /* activityOptions */);
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#startActivityInTaskFragment} on
+     * organized TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#startActivityInTaskFragment"})
+    public void testStartActivityInTaskFragment_organizedTaskFragment() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+        final IBinder callerToken = getActivityToken(activity);
+        final Intent intent = new Intent(mContext, TestActivity.class);
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .startActivityInTaskFragment(fragmentToken, callerToken, intent,
+                        null /* activityOptions */);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#requestFocusOnTaskFragment} on
+     * non-organized TaskFragment will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#requestFocusOnTaskFragment"})
+    public void testRequestFocusOnTaskFragment_nonOrganizedTaskFragment_throwException() {
+        // TODO(b/232476698) The TestApi is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .requestFocusOnTaskFragment(fragmentToken);
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#requestFocusOnTaskFragment} on
+     * organized TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#requestFocusOnTaskFragment"})
+    public void testRequestFocusOnTaskFragment_organizedTaskFragment() {
+        // TODO(b/232476698) The TestApi is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .requestFocusOnTaskFragment(fragmentToken);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#reparentActivityToTaskFragment} on
+     * non-organized TaskFragment will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#reparentActivityToTaskFragment"})
+    public void testReparentActivityToTaskFragment_nonOrganizedTaskFragment_throwException() {
+        // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+        final IBinder activityToken = getActivityToken(activity);
+
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .reparentActivityToTaskFragment(fragmentToken, activityToken);
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#reparentActivityToTaskFragment} on
+     * organized TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#startActivityInTaskFragment"})
+    public void testReparentActivityToTaskFragment_organizedTaskFragment() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+        final IBinder activityToken = getActivityToken(activity);
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .reparentActivityToTaskFragment(fragmentToken, activityToken);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#setAdjacentTaskFragments} on
+     * non-organized TaskFragment will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setAdjacentTaskFragments"})
+    public void testSetAdjacentTaskFragments_nonOrganizedTaskFragment_throwException() {
+        // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken0 = taskFragmentInfo0.getFragmentToken();
+        final IBinder fragmentToken1 = taskFragmentInfo1.getFragmentToken();
+
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setAdjacentTaskFragments(fragmentToken0, fragmentToken1, null /* params */);
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#setAdjacentTaskFragments} on
+     * organized TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setAdjacentTaskFragments"})
+    public void testSetAdjacentTaskFragments_organizedTaskFragment() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final IBinder fragmentToken0 = taskFragmentInfo0.getFragmentToken();
+        final IBinder fragmentToken1 = taskFragmentInfo1.getFragmentToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setAdjacentTaskFragments(fragmentToken0, fragmentToken1, null /* params */);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * Verifies that changing property on non-TaskFragment window will throw
      * {@link SecurityException}.
      */
-    @Test(expected = SecurityException.class)
-    public void testPerformOperationsOnNonOrganizedTaskFragment_ThrowException() {
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setBounds"})
+    public void testSetProperty_nonTaskFragmentWindow_throwException() {
+        final WindowContainerToken taskToken = getFirstTaskToken();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setBounds(taskToken, new Rect());
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that changing property on non-organized TaskFragment will throw
+     * {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setBounds"})
+    public void testSetProperty_nonOrganizedTaskFragment_throwException() {
         final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
 
-        // Create a TaskFragment with a TaskFragmentOrganizer.
-        final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(
-                getActivityToken(activity));
-        final IBinder taskFragToken = params.getFragmentToken();
-        WindowContainerTransaction wct = new WindowContainerTransaction()
-                .createTaskFragment(params);
+        // Create another TaskFragmentOrganizer to request operation.
+        final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setBounds(taskFragmentToken, new Rect());
+
+        assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that changing property on organized TaskFragment is allowed.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setBounds"})
+    public void testSetProperty_organizedTaskFragment() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setBounds(taskFragmentToken, new Rect());
+
         mTaskFragmentOrganizer.applyTransaction(wct);
-        // Wait for TaskFragment's creation to obtain its WindowContainerToken.
-        mTaskFragmentOrganizer.waitForTaskFragmentCreated();
+    }
 
-        // Create another TaskFragmentOrganizer
-        final TaskFragmentOrganizer anotherOrganizer = new TaskFragmentOrganizer(Runnable::run);
-        anotherOrganizer.registerOrganizer();
-        // Try to perform an operation on the TaskFragment when is organized by the previous
-        // TaskFragmentOrganizer.
-        wct = new WindowContainerTransaction()
-                .deleteTaskFragment(mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken)
-                        .getToken());
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#reparent} from
+     * TaskFragmentOrganizer will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#reparent"})
+    public void testDisallowOperation_reparent() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken0 = taskFragmentInfo0.getToken();
+        final WindowContainerToken taskFragmentToken1 = taskFragmentInfo1.getToken();
 
-        // It is expected to throw SecurityException when performing operations on the TaskFragment
-        // which is not organized by the same TaskFragmentOrganizer.
-        anotherOrganizer.applyTransaction(wct);
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .reparent(taskFragmentToken0, taskFragmentToken1, true /* onTop */);
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#reorder} from
+     * TaskFragmentOrganizer will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#reorder"})
+    public void testDisallowOperation_reorder() {
+        // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .reorder(taskFragmentToken, true /* onTop */);
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#setLaunchRoot} from
+     * TaskFragmentOrganizer will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setLaunchRoot"})
+    public void testDisallowOperation_setLaunchRoot() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setLaunchRoot(taskFragmentToken, null /* windowingModes */,
+                        null /* activityTypes */);
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#setLaunchAdjacentFlagRoot} from
+     * TaskFragmentOrganizer will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#setLaunchAdjacentFlagRoot"})
+    public void testDisallowOperation_setLaunchAdjacentFlagRoot() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .setLaunchAdjacentFlagRoot(taskFragmentToken);
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+    }
+
+    /**
+     * Verifies that performing {@link WindowContainerTransaction#clearLaunchAdjacentFlagRoot} from
+     * TaskFragmentOrganizer will throw {@link SecurityException}.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#clearLaunchAdjacentFlagRoot"})
+    public void testDisallowOperation_clearLaunchAdjacentFlagRoot() {
+        final Activity activity = startNewActivity();
+        final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+                mTaskFragmentOrganizer, activity);
+        final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .clearLaunchAdjacentFlagRoot(taskFragmentToken);
+
+        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
     }
 
     /**
@@ -349,15 +845,47 @@
             });
         }
     }
-
-    private static Activity startNewActivity() {
-        return startNewActivity(TestActivity.class);
+    /**
+     * Creates and registers a {@link TaskFragmentOrganizer} that will be unregistered in
+     * {@link #tearDown()}.
+     */
+    private BasicTaskFragmentOrganizer registerNewOrganizer() {
+        final BasicTaskFragmentOrganizer organizer = new BasicTaskFragmentOrganizer();
+        organizer.registerOrganizer();
+        mOrganizers.add(organizer);
+        return organizer;
     }
 
-    private static Activity startNewActivity(Class<?> className) {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        final Intent intent = new Intent(instrumentation.getTargetContext(), className)
-                .addFlags(FLAG_ACTIVITY_NEW_TASK);
-        return instrumentation.startActivitySync(intent);
+    /**
+     * Registers a {@link TaskOrganizer} to get the {@link WindowContainerToken} of a Task. The
+     * organizer will be unregistered in {@link #tearDown()}.
+     */
+    private WindowContainerToken getFirstTaskToken() {
+        final List<TaskAppearedInfo> taskInfos = new ArrayList<>();
+        // Register TaskOrganizer to obtain Task information.
+        NestedShellPermission.run(() -> {
+            mTaskOrganizer = new TaskOrganizer();
+            taskInfos.addAll(mTaskOrganizer.registerOrganizer());
+        });
+        return taskInfos.get(0).getTaskInfo().getToken();
+    }
+
+    /**
+     * Creates a TaskFragment organized by the given organizer. The TaskFragment will be removed
+     * when the organizer is unregistered.
+     */
+    private static TaskFragmentInfo createOrganizedTaskFragment(
+            BasicTaskFragmentOrganizer organizer, Activity ownerActivity) {
+        // Create a TaskFragment with a TaskFragmentOrganizer.
+        final TaskFragmentCreationParams params = organizer.generateTaskFragParams(
+                getActivityToken(ownerActivity));
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .createTaskFragment(params);
+        organizer.applyTransaction(wct);
+
+        // Wait for TaskFragment's creation to obtain its info.
+        organizer.waitForTaskFragmentCreated();
+        organizer.resetLatch();
+        return organizer.getTaskFragmentInfo(params.getFragmentToken());
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java
index 2621667..c9b7191 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -44,6 +45,8 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.compatibility.common.util.ApiTest;
+
 import org.junit.Test;
 
 /**
@@ -207,6 +210,28 @@
     }
 
     /**
+     * Verifies the behavior of {@link WindowContainerTransaction#finishActivity(IBinder)} to finish
+     * an Activity.
+     */
+    @Test
+    @ApiTest(apis = {
+            "android.window.TaskFragmentOrganizer#applyTransaction",
+            "android.window.WindowContainerTransaction#finishActivity"})
+    public void testFinishActivity() {
+        // TODO(b/232476698) The TestApi is new. Remove the assume in the next release.
+        assumeExtensionVersionAtLeast2();
+
+        final Activity activity = startNewActivity();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                .finishActivity(getActivityToken(activity));
+        mTaskFragmentOrganizer.applyTransaction(wct);
+
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+        assertTrue(activity.isDestroyed());
+    }
+
+    /**
      * Verifies the visibility of an activity behind a TaskFragment that has the same
      * bounds of the host Task.
      */
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
index 1ec0a11..93b9aff 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
@@ -17,14 +17,17 @@
 package android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -33,6 +36,7 @@
 import android.os.IBinder;
 import android.server.wm.WindowContextTests.TestActivity;
 import android.server.wm.WindowManagerState.WindowContainer;
+import android.server.wm.jetpack.utils.ExtensionUtil;
 import android.util.ArrayMap;
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentInfo;
@@ -41,6 +45,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.Before;
@@ -198,6 +203,23 @@
                 WINDOWING_MODE_UNDEFINED);
     }
 
+    static Activity startNewActivity() {
+        return startNewActivity(TestActivity.class);
+    }
+
+    static Activity startNewActivity(Class<?> className) {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Intent intent = new Intent(instrumentation.getTargetContext(), className)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK);
+        return instrumentation.startActivitySync(intent);
+    }
+
+    /** For API changes that are introduced together with WM Extensions version 2. */
+    static void assumeExtensionVersionAtLeast2() {
+        // TODO(b/232476698) Remove in the next Android release.
+        assumeTrue(ExtensionUtil.getExtensionVersion().getMajor() >= 2);
+    }
+
     public static class BasicTaskFragmentOrganizer extends TaskFragmentOrganizer {
         private final static int WAIT_TIMEOUT_IN_SECOND = 10;
 
@@ -257,7 +279,13 @@
         @NonNull
         public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken,
                 @NonNull Rect bounds, int windowingMode) {
-            return new TaskFragmentCreationParams.Builder(getOrganizerToken(), new Binder(),
+            return generateTaskFragParams(new Binder(), ownerToken, bounds, windowingMode);
+        }
+
+        @NonNull
+        public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder fragmentToken,
+                @NonNull IBinder ownerToken, @NonNull Rect bounds, int windowingMode) {
+            return new TaskFragmentCreationParams.Builder(getOrganizerToken(), fragmentToken,
                     ownerToken)
                     .setInitialBounds(bounds)
                     .setWindowingMode(windowingMode)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java
index 9fd1a41..d3bd635 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java
@@ -21,7 +21,6 @@
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.assumeActivityEmbeddingSupportedDevice;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -251,7 +250,7 @@
      */
     @Test
     public void testUntrustedModeTaskFragment_startActivityInTaskFragmentOutsideOfParentBounds() {
-        Task parentTask = mWmState.getRootTask(mOwnerTaskId);
+        final Task parentTask = mWmState.getRootTask(mOwnerTaskId);
         final Rect parentBounds = new Rect(parentTask.getBounds());
         final IBinder errorCallbackToken = new Binder();
         final WindowContainerTransaction wct = new WindowContainerTransaction()
@@ -266,11 +265,6 @@
         // It is disallowed to start activity to TaskFragment with bounds outside of its parent
         // in untrusted mode.
         assertTaskFragmentError(errorCallbackToken, SecurityException.class);
-
-        parentTask = mWmState.getRootTask(mOwnerTaskId);
-        assertWithMessage("Activity must be started in parent Task because it's not"
-                + " allowed to be embedded").that(parentTask.mActivities).contains(
-                mWmState.getActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY));
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
index 0281f55..cfa91f5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
@@ -33,7 +33,6 @@
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
@@ -51,6 +50,7 @@
 import android.animation.ValueAnimator;
 import android.app.Instrumentation;
 import android.graphics.Insets;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.WindowInsetsAnimationTestBase.TestActivity;
@@ -105,7 +105,7 @@
 @RunWith(Parameterized.class)
 public class WindowInsetsAnimationControllerTests extends WindowManagerTestBase {
 
-    TestActivity mActivity;
+    ControllerTestActivity mActivity;
     View mRootView;
     ControlListener mListener;
     CancellationSignal mCancellationSignal = new CancellationSignal();
@@ -139,6 +139,16 @@
         };
     }
 
+    public static class ControllerTestActivity extends TestActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            // Ensure to set animation callback to null before starting a test. Otherwise, launching
+            // this activity might trigger some inset animation accidentally.
+            mView.setWindowInsetsAnimationCallback(null);
+        }
+    }
+
     @Before
     public void setUpWindowInsetsAnimationControllerTests() throws Throwable {
         assumeFalse(
@@ -152,7 +162,8 @@
             assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
                     nullValue());
 
-            // For the best test stability MockIme should be selected before launching TestActivity.
+            // For the best test stability MockIme should be selected before launching
+            // ControllerTestActivity.
             mMockImeSession = MockImeSession.create(
                     instrumentation.getContext(), instrumentation.getUiAutomation(),
                     new ImeSettings.Builder());
@@ -161,14 +172,15 @@
             mockImeEventStream = null;
         }
 
-        mActivity = startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);
+        mActivity = startActivityInWindowingMode(ControllerTestActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
         mRootView = mActivity.getWindow().getDecorView();
         mListener = new ControlListener(mErrorCollector);
         assumeTestCompatibility();
 
         if (mockImeEventStream != null) {
-            // TestActivity has a focused EditText. Hence MockIme should receive onStartInput() for
-            // that EditText within a reasonable time.
+            // ControllerTestActivity has a focused EditText. Hence MockIme should receive
+            // onStartInput() for that EditText within a reasonable time.
             expectEvent(mockImeEventStream,
                     editorMatcher("onStartInput", mActivity.getEditTextMarker()),
                     TimeUnit.SECONDS.toMillis(10));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
index 87c0fc8..2fcf5ce 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
@@ -258,7 +258,8 @@
 
         waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
 
-        assertFalse(getWmState().isWindowVisible("StatusBar"));
+        mWmState.computeState();
+        assertFalse(mWmState.isWindowVisible("StatusBar"));
         verify(mActivity.mCallback).onPrepare(any());
         verify(mActivity.mCallback).onStart(any(), any());
         verify(mActivity.mCallback, atLeastOnce()).onProgress(any(), any());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerReflectionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerReflectionTests.java
new file mode 100644
index 0000000..63bbcda
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerReflectionTests.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+package android.server.wm;
+
+
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+
+import java.lang.reflect.InvocationTargetException;
+
+@Presubmit
+public class WindowManagerReflectionTests {
+
+    /** Regression test for b/273906410. */
+    @Test
+    public void requestAppKeyboardShortcuts_requiresPermission_b273906410() throws Throwable {
+        Object wms = Class.forName("android.view.WindowManagerGlobal")
+                .getMethod("getWindowManagerService")
+                .invoke(null);
+
+        assertThrows(SecurityException.class, () -> {
+            runAndUnwrapTargetException(() -> {
+                Class.forName("android.view.IWindowManager")
+                        .getMethod("requestAppKeyboardShortcuts",
+                                Class.forName("com.android.internal.os.IResultReceiver"),
+                                Integer.TYPE)
+                        .invoke(wms, null, 0);
+            });
+        });
+    }
+
+    private void runAndUnwrapTargetException(ThrowingRunnable r) throws Throwable {
+        try {
+            r.run();
+        } catch (InvocationTargetException e) {
+            throw e.getTargetException();
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index 2c6a21f..a5f0beb 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -91,6 +91,7 @@
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.LaunchingActivity.KEY_FINISH_BEFORE_LAUNCH;
 import static android.server.wm.app.Components.PipActivity.ACTION_CHANGE_ASPECT_RATIO;
+import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
 import static android.server.wm.app.Components.PipActivity.ACTION_UPDATE_PIP_STATE;
@@ -191,6 +192,8 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
@@ -433,6 +436,19 @@
                     .putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true));
         }
 
+        void enterPipAndWait() {
+            try {
+                final CompletableFuture<Boolean> future = new CompletableFuture<>();
+                final RemoteCallback remoteCallback = new RemoteCallback(
+                        (Bundle result) -> future.complete(true));
+                mContext.sendBroadcast(createIntentWithAction(ACTION_ENTER_PIP)
+                        .putExtra(EXTRA_SET_PIP_CALLBACK, remoteCallback));
+                assertTrue(future.get(5000, TimeUnit.MILLISECONDS));
+            } catch (Exception e) {
+                logE("enterPipAndWait failed", e);
+            }
+        }
+
         void expandPip() {
             mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP));
         }
@@ -628,6 +644,8 @@
             pressUnlockButton();
         }
         launchHomeActivityNoWait();
+        // TODO(b/242933292): Consider removing all the tasks belonging to android.server.wm
+        // instead of removing all and then waiting for allActivitiesResumed.
         removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
         runWithShellPermission(() -> {
@@ -637,6 +655,14 @@
             // state.
             mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
         });
+
+        // removeRootTaskWithActivityTypes() removes all the tasks apart from home. In a few cases,
+        // the systemUI might have a few tasks that need to be displayed all the time.
+        // For such tasks, systemUI might have a restart-logic that restarts those tasks. Those
+        // restarts can interfere with the test state. To avoid that, its better to wait for all
+        // the activities to come in the resumed state.
+        mWmState.waitForWithAmState(WindowManagerState::allActivitiesResumed, "Root Tasks should "
+                + "be either empty or resumed");
     }
 
     /** It always executes after {@link org.junit.After}. */
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateUtils.java b/tests/framework/base/windowmanager/util/src/android/server/wm/DeviceStateUtils.java
similarity index 92%
rename from tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateUtils.java
rename to tests/framework/base/windowmanager/util/src/android/server/wm/DeviceStateUtils.java
index df97832..55d0926 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateUtils.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/DeviceStateUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.devicestate.cts;
+package android.server.wm;
 
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -26,7 +26,7 @@
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.ThrowingRunnable;
 
-/** Utility methods for {@DeviceStateManager} CTS tests. */
+/** Utility methods for CTS tests requiring the use of {@DeviceStateManager}. */
 public final class DeviceStateUtils {
     /**
      * Runs the supplied {@code runnable} with the
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/DisplayMetricsSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/DisplayMetricsSession.java
new file mode 100644
index 0000000..86eea03
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/DisplayMetricsSession.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.server.wm.ActivityManagerTestBase.ReportedDisplayMetrics;
+
+import android.util.Size;
+
+/**
+ * A test helper class that queries or changes the latest DisplayMetrics override in WM by
+ * sending Shell wm commands. Provides handling for errors, orientation types and fallback
+ * behaviors.
+ * Example usage: This can be used to resize a Display in CTS test and trigger configuration
+ * changes.
+ */
+public class DisplayMetricsSession implements AutoCloseable {
+    private final ReportedDisplayMetrics mInitialDisplayMetrics;
+    private final int mDisplayId;
+
+    public DisplayMetricsSession(int displayId) {
+        mDisplayId = displayId;
+        mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(
+                mDisplayId);
+    }
+
+    ReportedDisplayMetrics getInitialDisplayMetrics() {
+        return mInitialDisplayMetrics;
+    }
+
+    ReportedDisplayMetrics getDisplayMetrics() {
+        return ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
+    }
+
+    void changeAspectRatio(double aspectRatio, int orientation) {
+        final Size originalSize = mInitialDisplayMetrics.physicalSize;
+        final int smaller = Math.min(originalSize.getWidth(), originalSize.getHeight());
+        final int larger = (int) (smaller * aspectRatio);
+        Size overrideSize;
+        if (orientation == ORIENTATION_LANDSCAPE) {
+            overrideSize = new Size(larger, smaller);
+        } else {
+            overrideSize = new Size(smaller, larger);
+        }
+        overrideDisplayMetrics(overrideSize, mInitialDisplayMetrics.physicalDensity);
+    }
+
+    public void changeDisplayMetrics(double sizeRatio, double densityRatio) {
+        // Given a display may already have an override applied before the test is begun,
+        // resize based upon the override.
+        final Size originalSize;
+        final int density;
+        if (mInitialDisplayMetrics.overrideSize != null) {
+            originalSize = mInitialDisplayMetrics.overrideSize;
+        } else {
+            originalSize = mInitialDisplayMetrics.physicalSize;
+        }
+
+        if (mInitialDisplayMetrics.overrideDensity != null) {
+            density = mInitialDisplayMetrics.overrideDensity;
+        } else {
+            density = mInitialDisplayMetrics.physicalDensity;
+        }
+
+        final Size overrideSize = new Size((int)(originalSize.getWidth() * sizeRatio),
+                (int)(originalSize.getHeight() * sizeRatio));
+        final int overrideDensity = (int)(density * densityRatio);
+        overrideDisplayMetrics(overrideSize, overrideDensity);
+    }
+
+    void overrideDisplayMetrics(final Size size, final int density) {
+        mInitialDisplayMetrics.setDisplayMetrics(size, density);
+    }
+
+    public void restoreDisplayMetrics() {
+        mInitialDisplayMetrics.restoreDisplayMetrics();
+    }
+
+    @Override
+    public void close() {
+        restoreDisplayMetrics();
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java b/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
index 3cdd57a..ced0497 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
@@ -25,14 +25,13 @@
 import com.android.compatibility.common.util.SystemUtil;
 
 public class DreamCoordinator {
-    private Context mContext;
+    private final DreamManager mDreamManager;
+
     private boolean mSetup;
     private boolean mDefaultDreamServiceEnabled;
-    private DreamManager mDreamManager;
 
     public DreamCoordinator(Context context) {
-        mContext = context;
-        mDreamManager = mContext.getSystemService(DreamManager.class);
+        mDreamManager = context.getSystemService(DreamManager.class);
     }
 
     public final ComponentName getDreamActivityName(ComponentName dream) {
@@ -60,7 +59,7 @@
      * Restores any settings changed by {@link #setup()}.
      */
     public void restoreDefaults() {
-        // If we have not setup the coordinator, do not do anything.
+        // If we have not set up the coordinator, do not do anything.
         if (!mSetup) {
             return;
         }
@@ -76,24 +75,30 @@
                 () -> mDreamManager.setScreensaverEnabled(false));
     }
 
-    public void startDream(ComponentName name) {
-        SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.startDream(name));
+    public void startDream() {
+        SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.startDream());
     }
 
     public void stopDream() {
-        SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.stopDream());
+        SystemUtil.runWithShellPermissionIdentity(mDreamManager::stopDream);
     }
 
-    public  ComponentName setActiveDream(ComponentName dream) {
+    public ComponentName setActiveDream(ComponentName dream) {
         SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.setActiveDream(dream));
         return getDreamActivityName(dream);
     }
 
+    public ComponentName setSystemDream(ComponentName dream) {
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                mDreamManager.setSystemDreamComponent(dream));
+        return dream == null ? null : getDreamActivityName(dream);
+    }
+
     public void setDreamOverlay(ComponentName overlay) {
         SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.setDreamOverlay(overlay));
     }
 
     public boolean isDreaming() {
-        return SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.isDreaming());
+        return SystemUtil.runWithShellPermissionIdentity(mDreamManager::isDreaming);
     }
 }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 6f6edf9..7ee2b81 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -976,6 +976,15 @@
                 .collect(Collectors.toList());
     }
 
+    public boolean allActivitiesResumed() {
+        for (Task rootTask : mRootTasks) {
+            final Activity nonResumedActivity =
+                    rootTask.getActivity((a) -> !a.state.equals(STATE_RESUMED));
+            if (nonResumedActivity != null) return false;
+        }
+        return true;
+    }
+
     public boolean hasNotificationShade() {
         computeState();
         return !getMatchingWindowType(TYPE_NOTIFICATION_SHADE).isEmpty();
@@ -1225,27 +1234,23 @@
             //                    don't treat them as regular root tasks
             collectDescendantsOfTypeIf(Task.class, t -> t.isRootTask(), this,
                     mRootTasks);
-            ArrayList<Task> rootOrganizedTasks = new ArrayList<>();
-            for (int i = mRootTasks.size() -1; i >= 0; --i) {
+
+            ArrayList<Task> nonOrganizedRootTasks = new ArrayList<>();
+            for (int i = 0; i < mRootTasks.size(); i++) {
                 final Task task = mRootTasks.get(i);
-                // Skip tasks created by an organizer
                 if (task.mCreatedByOrganizer) {
-                    mRootTasks.remove(task);
-                    rootOrganizedTasks.add(task);
+                    // Get all tasks inside the root-task created by an organizer
+                    List<Task> nonOrganizedDescendants = new ArrayList<>();
+                    collectDescendantsOfTypeIf(Task.class, t -> !t.mCreatedByOrganizer, task,
+                            nonOrganizedDescendants);
+                    nonOrganizedRootTasks.addAll(nonOrganizedDescendants);
+                } else {
+                    nonOrganizedRootTasks.add(task);
                 }
             }
-            // Add root tasks controlled by an organizer
-            while (rootOrganizedTasks.size() > 0) {
-                final Task task = rootOrganizedTasks.remove(0);
-                for (int i = task.mChildren.size() - 1; i >= 0; i--) {
-                    final Task child = (Task) task.mChildren.get(i);
-                    if (!child.mCreatedByOrganizer) {
-                        mRootTasks.add(child);
-                    } else {
-                        rootOrganizedTasks.add(child);
-                    }
-                }
-            }
+
+            mRootTasks.clear();
+            mRootTasks.addAll(nonOrganizedRootTasks);
         }
 
         boolean containsActivity(ComponentName activityName) {
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 0913831..a6d5f76 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -190,6 +190,13 @@
                 "Keyguard showing and occluded");
     }
 
+    void waitAndAssertWindowShown(int windowType, boolean show) {
+        assertTrue(waitFor(state -> {
+            WindowState w = state.findFirstWindowWithType(windowType);
+            return w != null && w.isSurfaceShown() == show;
+        }, "wait for window surface " + (show ? "show" : "hide")));
+    }
+
     public void waitForAodShowing() {
         waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing, "AOD showing");
     }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java b/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java
index fae5657..d60fc0d 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java
@@ -48,7 +48,11 @@
         }
     }
 
-    void waitAndAssertActivityCurrentState(Class<? extends Activity> activityClass,
+    /**
+     * Blocking call that will wait and verify that the activity transition settles with the
+     * expected state.
+     */
+    public void waitAndAssertActivityCurrentState(Class<? extends Activity> activityClass,
             String expectedState) {
         final boolean waitResult = waitForConditionWithTimeout(() -> {
             List<String> activityLog = mEventLog.getActivityLog(activityClass);
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index 63f826f..5d00e88 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -31,7 +31,6 @@
         "androidx.test.rules",
         "androidx.test.uiautomator_uiautomator",
         "compatibility-device-util-axt",
-        "cts-wm-util",
         "cts-inputmethod-util",
         "cts-mock-a11y-ime-client",
         "ctstestrunner-axt",
@@ -41,6 +40,7 @@
         "testng",
         "kotlin-test",
         "cts-wm-util",
+        "cts_window-extensions",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 0211434..79203ee 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -29,8 +29,8 @@
 
         <uses-library android:name="android.test.runner"/>
 
-        <!-- TestActivity etc are merged from util/AndroidManifest.xml -->
-
+        <uses-library android:name="androidx.window.extensions"
+                      android:required="false" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/inputmethod/mockime/Android.bp b/tests/inputmethod/mockime/Android.bp
index 5ee0505..ba03f4f 100644
--- a/tests/inputmethod/mockime/Android.bp
+++ b/tests/inputmethod/mockime/Android.bp
@@ -30,6 +30,8 @@
         "androidx.annotation_annotation",
         "androidx.autofill_autofill",
         "compatibility-device-util-axt",
+        "cts_window_jetpack_utils",
+        "cts_window-extensions",
     ],
 }
 
diff --git a/tests/inputmethod/mockime/AndroidManifest.xml b/tests/inputmethod/mockime/AndroidManifest.xml
index e695412..cab88f8 100644
--- a/tests/inputmethod/mockime/AndroidManifest.xml
+++ b/tests/inputmethod/mockime/AndroidManifest.xml
@@ -18,6 +18,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.android.cts.mockime">
 
+    <queries>
+        <package android:name="com.android.cts.spellcheckingime" />
+    </queries>
+
     <application android:multiArch="true"
          android:supportsRtl="true"
          android:debuggable="true">
@@ -41,6 +45,7 @@
              android:exported="true"
              android:visibleToInstantApps="true">
         </provider>
-
+        <uses-library android:name="androidx.window.extensions"
+                      android:required="false" />
     </application>
 </manifest>
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index cb49460..cb0dd06 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.mockime;
 
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Pair;
@@ -23,7 +26,13 @@
 import android.view.inputmethod.InputBinding;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Predicate;
@@ -443,4 +452,84 @@
         }
         return stream.copy();
     }
+
+    /**
+     *  A copy of {@link WindowLayoutInfo} class just for the purpose of testing with MockIME
+     *  test setup.
+     *  This is because only in this setup we will pass {@link WindowLayoutInfo} through
+     *  different processes.
+     */
+    public static class WindowLayoutInfoParcelable implements Parcelable {
+        private List<DisplayFeature> mDisplayFeatures = new ArrayList<DisplayFeature>();
+
+        public WindowLayoutInfoParcelable(WindowLayoutInfo windowLayoutInfo) {
+            this.mDisplayFeatures = windowLayoutInfo.getDisplayFeatures();
+        }
+        public WindowLayoutInfoParcelable(Parcel in) {
+            while (in.dataAvail() > 0) {
+                Rect bounds;
+                int type = -1, state = -1;
+                bounds = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
+                type = in.readInt();
+                state = in.readInt();
+                mDisplayFeatures.add(new FoldingFeature(bounds, type, state));
+            }
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof WindowLayoutInfoParcelable)) {
+                return false;
+            }
+
+            List<androidx.window.extensions.layout.DisplayFeature> listA =
+                    this.getDisplayFeatures();
+            List<DisplayFeature> listB = ((WindowLayoutInfoParcelable) o).getDisplayFeatures();
+            if (listA.size() != listB.size()) return false;
+            for (int i = 0; i < listA.size(); ++i) {
+                if (!listA.get(i).equals(listB.get(i))) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            // The actual implementation is FoldingFeature, DisplayFeature is an abstract class.
+            mDisplayFeatures.forEach(feature -> {
+                        dest.writeParcelable(feature.getBounds(), flags);
+                        dest.writeInt(((FoldingFeature) feature).getType());
+                        dest.writeInt(((FoldingFeature) feature).getState());
+                    }
+            );
+        }
+
+        public List<DisplayFeature> getDisplayFeatures() {
+            return mDisplayFeatures;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Parcelable.Creator<WindowLayoutInfoParcelable> CREATOR =
+                new Parcelable.Creator<WindowLayoutInfoParcelable>() {
+
+                    @Override
+                    public WindowLayoutInfoParcelable createFromParcel(Parcel in) {
+                        return new WindowLayoutInfoParcelable(in);
+                    }
+
+                    @Override
+                    public WindowLayoutInfoParcelable[] newArray(int size) {
+                        return new WindowLayoutInfoParcelable[size];
+                    }
+                };
+    }
 }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index cfc30ec..eea1727 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -25,6 +25,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import java.lang.annotation.Retention;
 
@@ -60,6 +61,8 @@
             "InlineSuggestionViewContentDesc";
     private static final String STRICT_MODE_ENABLED = "StrictModeEnabled";
     private static final String VERIFY_CONTEXT_APIS_IN_ON_CREATE = "VerifyContextApisInOnCreate";
+    private static final String WINDOW_LAYOUT_INFO_CALLBACK_ENABLED =
+            "WindowLayoutInfoCallbackEnabled";
 
     /**
      * Simulate the manifest flag enableOnBackInvokedCallback being true for the IME.
@@ -187,6 +190,10 @@
         return mBundle.getBoolean(VERIFY_CONTEXT_APIS_IN_ON_CREATE, false);
     }
 
+    public boolean isWindowLayoutInfoCallbackEnabled() {
+        return mBundle.getBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, false);
+    }
+
     public boolean isOnBackCallbackEnabled() {
         return mBundle.getBoolean(ON_BACK_CALLBACK_ENABLED, false);
     }
@@ -374,6 +381,14 @@
         }
 
         /**
+         * Sets whether to enable {@link WindowLayoutInfo} callbacks for {@link MockIme}.
+         */
+        public Builder setWindowLayoutInfoCallbackEnabled(boolean enabled) {
+            mBundle.putBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, enabled);
+            return this;
+        }
+
+        /**
          * Sets whether the IME's
          * {@link android.content.pm.ApplicationInfo#isOnBackInvokedCallbackEnabled()}
          * should be set to {@code true}.
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 528da58..ea168f4 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.mockime;
 
+import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -65,6 +66,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.TextAttribute;
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
@@ -82,6 +84,9 @@
 import androidx.autofill.inline.UiVersions;
 import androidx.autofill.inline.UiVersions.StylesBuilder;
 import androidx.autofill.inline.v1.InlineSuggestionUi;
+import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import java.util.ArrayList;
 import java.util.concurrent.ExecutorService;
@@ -117,6 +122,14 @@
         return eventActionName + ".command";
     }
 
+    @Nullable
+    private final WindowExtensions mWindowExtensions = getWindowExtensions();
+    @Nullable
+    private final WindowLayoutComponent mWindowLayoutComponent =
+            mWindowExtensions != null ? mWindowExtensions.getWindowLayoutComponent() : null;
+
+    private Consumer<WindowLayoutInfo> mWindowLayoutInfoConsumer;
+
     private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver");
 
     private final Handler mMainHandler = new Handler();
@@ -396,6 +409,26 @@
                         return getMemorizedOrCurrentInputConnection().setImeConsumesInput(
                                 imeConsumesInput);
                     }
+                    case "switchInputMethod": {
+                        final String id = command.getExtras().getString("id");
+                        try {
+                            switchInputMethod(id);
+                        } catch (Exception e) {
+                            return e;
+                        }
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "switchInputMethod(String,InputMethodSubtype)": {
+                        final String id = command.getExtras().getString("id");
+                        final InputMethodSubtype subtype = command.getExtras().getParcelable(
+                                "subtype", InputMethodSubtype.class);
+                        try {
+                            switchInputMethod(id, subtype);
+                        } catch (Exception e) {
+                            return e;
+                        }
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
                     case "setBackDisposition": {
                         final int backDisposition =
                                 command.getExtras().getInt("backDisposition");
@@ -692,6 +725,11 @@
             } else {
                 registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
             }
+            // If Extension components are not loaded successfully, notify Test app.
+            if (mSettings.isWindowLayoutInfoCallbackEnabled()) {
+                getTracer().onVerify("windowLayoutComponentLoaded",
+                        () -> mWindowLayoutComponent != null);
+            }
             if (mSettings.isVerifyContextApisInOnCreate()) {
                 getTracer().onVerify("isUiContext", this::verifyIsUiContext);
                 getTracer().onVerify("getDisplay", this::verifyGetDisplay);
@@ -726,6 +764,12 @@
             // in IME event.
             mLastDispatchedConfiguration.setTo(getResources().getConfiguration());
         });
+
+        if (mSettings.isWindowLayoutInfoCallbackEnabled() && mWindowLayoutComponent != null) {
+            mWindowLayoutInfoConsumer = (windowLayoutInfo) -> getTracer().getWindowLayoutInfo(
+                    windowLayoutInfo, () -> {});
+            mWindowLayoutComponent.addWindowLayoutInfoListener(this, mWindowLayoutInfoConsumer);
+        }
     }
 
     @Override
@@ -1060,6 +1104,9 @@
         getTracer().onDestroy(() -> {
             mDestroying = true;
             super.onDestroy();
+            if (mSettings.isWindowLayoutInfoCallbackEnabled() && mWindowLayoutComponent != null) {
+                mWindowLayoutComponent.removeWindowLayoutInfoListener(mWindowLayoutInfoConsumer);
+            }
             unregisterReceiver(mCommandReceiver);
             mHandlerThread.quitSafely();
         });
@@ -1202,6 +1249,8 @@
 
     @Override
     public void onConfigurationChanged(Configuration configuration) {
+        // Broadcasting configuration change is implemented at WindowProviderService level.
+        super.onConfigurationChanged(configuration);
         getTracer().onConfigurationChanged(() -> {}, configuration);
         mLastDispatchedConfiguration.setTo(configuration);
     }
@@ -1517,5 +1566,14 @@
                     mIme.mLastDispatchedConfiguration));
             recordEventInternal("onConfigurationChanged", runnable, arguments);
         }
+
+        void getWindowLayoutInfo(@NonNull WindowLayoutInfo windowLayoutInfo,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            ImeEventStreamTestUtils.WindowLayoutInfoParcelable parcel =
+                    new ImeEventStreamTestUtils.WindowLayoutInfoParcelable(windowLayoutInfo);
+            arguments.putParcelable("WindowLayoutInfo", parcel);
+            recordEventInternal("getWindowLayoutInfo", runnable, arguments);
+        }
     }
 }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 56795ad..8a61977 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -46,6 +46,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.TextAttribute;
 
 import androidx.annotation.AnyThread;
@@ -1311,6 +1312,44 @@
     }
 
     /**
+     * Makes {@link MockIme} call {@link
+     * android.inputmethodservice.InputMethodService#switchInputMethod(String)}
+     * with the given parameters.
+     *
+     * @param id the IME ID.
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callSwitchInputMethod(String id) {
+        final Bundle params = new Bundle();
+        params.putString("id", id);
+        return callCommandInternal("switchInputMethod", params);
+    }
+
+    /**
+     * Lets {@link MockIme} to call {@link
+     * android.inputmethodservice.InputMethodService#switchInputMethod(String, InputMethodSubtype)}
+     * with the given parameters.
+     *
+     * <p>This triggers {@code switchInputMethod(id, subtype)}.</p>
+     *
+     * @param id the IME ID.
+     * @param subtype {@link InputMethodSubtype} to be switched to. Ignored if {@code null}.
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callSwitchInputMethod(String id, @Nullable InputMethodSubtype subtype) {
+        final Bundle params = new Bundle();
+        params.putString("id", id);
+        params.putParcelable("subtype", subtype);
+        return callCommandInternal("switchInputMethod(String,InputMethodSubtype)", params);
+    }
+
+    /**
      * Lets {@link MockIme} to call
      * {@link android.inputmethodservice.InputMethodService#setBackDisposition(int)} with the given
      * parameters.
@@ -1496,6 +1535,11 @@
     }
 
     @NonNull
+    public ImeCommand callGetWindowLayoutInfo() {
+        return callCommandInternal("getWindowLayoutInfo", new Bundle());
+    }
+
+    @NonNull
     public ImeCommand callSetStylusHandwritingInkView() {
         return callCommandInternal("setStylusHandwritingInkView", new Bundle());
     }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index a9cec51..680aded 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.server.wm.jetpack.utils.ExtensionUtil.EXTENSION_VERSION_2;
+import static android.server.wm.jetpack.utils.ExtensionUtil.isExtensionVersionAtLeast;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
@@ -29,6 +31,7 @@
 import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_EXIT_EVENT_ONLY;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.WindowLayoutInfoParcelable;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
@@ -41,6 +44,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
@@ -48,10 +52,12 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.server.wm.DisplayMetricsSession;
 import android.support.test.uiautomator.UiObject2;
 import android.text.TextUtils;
 import android.view.KeyCharacterMap;
@@ -79,7 +85,10 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
+import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.mockime.ImeCommand;
 import com.android.cts.mockime.ImeEvent;
@@ -88,6 +97,7 @@
 import com.android.cts.mockime.MockImeSession;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -111,6 +121,7 @@
     private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
     private static final long ACTIVITY_LAUNCH_INTERVAL = 500;  // msec
 
+    private static final String OTHER_IME_ID = "com.android.cts.spellcheckingime/.SpellCheckingIme";
 
     private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
     // 1.2 is an arbitrary value.
@@ -183,6 +194,53 @@
         }
     }
 
+    @Test
+    public void testSwitchInputMethod_verifiesEnabledState() throws Exception {
+        SystemUtil.runShellCommand("ime disable " + OTHER_IME_ID);
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+            final ImeCommand cmd = imeSession.callSwitchInputMethod(OTHER_IME_ID);
+            final ImeEvent event = expectCommand(stream, cmd, TIMEOUT);
+            assertTrue("should be exception result, but wasn't" + event,
+                    event.isExceptionReturnValue());
+            // Should be IllegalStateException, but CompletableFuture converts to RuntimeException
+            assertTrue("should be RuntimeException, but wasn't: "
+                            + event.getReturnExceptionValue(),
+                    event.getReturnExceptionValue() instanceof RuntimeException);
+            assertTrue(
+                    "should contain 'not enabled' but didn't: " + event.getReturnExceptionValue(),
+                    event.getReturnExceptionValue().getMessage().contains("not enabled"));
+        }
+    }
+    @Test
+    public void testSwitchInputMethodWithSubtype_verifiesEnabledState() throws Exception {
+        SystemUtil.runShellCommand("ime disable " + OTHER_IME_ID);
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+            final ImeCommand cmd = imeSession.callSwitchInputMethod(OTHER_IME_ID, null);
+            final ImeEvent event = expectCommand(stream, cmd, TIMEOUT);
+            assertTrue("should be exception result, but wasn't" + event,
+                    event.isExceptionReturnValue());
+            // Should be IllegalStateException, but CompletableFuture converts to RuntimeException
+            assertTrue("should be RuntimeException, but wasn't: "
+                            + event.getReturnExceptionValue(),
+                    event.getReturnExceptionValue() instanceof RuntimeException);
+            assertTrue(
+                    "should contain 'not enabled' but didn't: " + event.getReturnExceptionValue(),
+                    event.getReturnExceptionValue().getMessage().contains("not enabled"));
+        }
+    }
+
     private void verifyImeConsumesBackButton(int backDisposition) throws Exception {
         try (MockImeSession imeSession = MockImeSession.create(
                 InstrumentationRegistry.getInstrumentation().getContext(),
@@ -794,6 +852,121 @@
         }
     }
 
+    /**
+     * Starts a {@link MockImeSession} and verifies MockIme receives {@link WindowLayoutInfo}
+     * updates. Trigger Configuration changes by modifying the DisplaySession where MockIME window
+     * is located, then verify Bounds from MockIME window and {@link DisplayFeature} from
+     * WindowLayoutInfo updates observe the same changes to the hinge location.
+     * Here we use {@link WindowLayoutInfoParcelable} to pass {@link WindowLayoutInfo} values
+     * between this test process and the MockIME process.
+     */
+    @Ignore("b/264026686")
+    @Test
+    @ApiTest(apis = {
+            "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+    public void testImeListensToWindowLayoutInfo() throws Exception {
+        assumeTrue(
+                "This test should only be run on devices with extension version that supports IME"
+                        + " as WindowLayoutInfo listener ",
+                isExtensionVersionAtLeast(EXTENSION_VERSION_2));
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setWindowLayoutInfoCallbackEnabled(true))) {
+
+            final ImeEventStream stream = imeSession.openEventStream();
+            TestActivity activity = createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+            assertTrue(expectEvent(stream, verificationMatcher("windowLayoutComponentLoaded"),
+                    CHECK_EXIT_EVENT_ONLY, TIMEOUT).getReturnBooleanValue());
+
+            try (DisplayMetricsSession displaySession = new DisplayMetricsSession(
+                    activity.getDisplay().getDisplayId())) {
+
+                final double displayResizeRatio = 0.8;
+
+                // MockIME has registered addWindowLayoutInfo, it should be emitting the
+                // current location of hinge now.
+                WindowLayoutInfoParcelable windowLayoutInit = verifyReceivedWindowLayout(
+                        stream);
+                // Skip the test if the device doesn't support hinges.
+                assertNotNull(windowLayoutInit);
+                assertNotNull(windowLayoutInit.getDisplayFeatures());
+                assumeFalse(windowLayoutInit.getDisplayFeatures().isEmpty());
+
+                final Rect windowLayoutInitBounds = windowLayoutInit.getDisplayFeatures().get(0)
+                        .getBounds();
+
+                expectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+                        TIMEOUT);
+                expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                        TIMEOUT);
+
+                // After IME is shown, get the bounds of IME.
+                final Rect imeBoundsInit = expectCommand(stream,
+                        imeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+                        .getReturnParcelableValue();
+                // Contain first part of the test in a try-block so that the display session
+                // could be restored for the remaining testsuite even if something fails.
+                try {
+                    // Shrink the entire display 20% smaller.
+                    displaySession.changeDisplayMetrics(displayResizeRatio /* sizeRatio */,
+                            1.0 /* densityRatio */);
+
+                    // onConfigurationChanged on WM side triggers a new calculation for
+                    // hinge location.
+                    WindowLayoutInfoParcelable windowLayoutSizeChange = verifyReceivedWindowLayout(
+                            stream);
+
+                    // Expect to receive same number of display features in WindowLayoutInfo.
+                    assertEquals(windowLayoutInit.getDisplayFeatures().size(),
+                            windowLayoutSizeChange.getDisplayFeatures().size());
+
+                    Rect windowLayoutSizeChangeBounds =
+                            windowLayoutSizeChange.getDisplayFeatures().get(
+                                    0).getBounds();
+                    Rect imeBoundsShrunk = expectCommand(stream,
+                            imeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+                            .getReturnParcelableValue();
+
+                    final Boolean widthsChangedInSameRatio =
+                            (windowLayoutInitBounds.width() * displayResizeRatio
+                                    == windowLayoutSizeChangeBounds.width() && (
+                                    imeBoundsInit.width() * displayResizeRatio
+                                            == imeBoundsShrunk.width()));
+                    final Boolean heightsChangedInSameRatio =
+                            (windowLayoutInitBounds.height() * displayResizeRatio
+                                    == windowLayoutSizeChangeBounds.height() && (
+                                    imeBoundsInit.height() * displayResizeRatio
+                                            == imeBoundsShrunk.height()));
+                    // Expect the hinge dimension to shrink in exactly one direction, the actual
+                    // dimension depends on device implementation. Observe hinge dimensions from
+                    // IME configuration bounds and from WindowLayoutInfo.
+                    assertTrue(widthsChangedInSameRatio || heightsChangedInSameRatio);
+                } finally {
+                    // Restore Display to original size.
+                    displaySession.restoreDisplayMetrics();
+                    // Advance stream to ignore unrelated side effect from WM configuration changes.
+                    // TODO(b/257990185): Add filtering in WM Extensions to remove this.
+                    stream.skipAll();
+
+                    WindowLayoutInfoParcelable windowLayoutRestored = verifyReceivedWindowLayout(
+                            stream);
+
+                    assertEquals(windowLayoutInitBounds,
+                            windowLayoutRestored.getDisplayFeatures().get(0).getBounds());
+
+                    final Rect imeBoundsRestored = expectCommand(stream,
+                            imeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+                            .getReturnParcelableValue();
+
+                    assertEquals(imeBoundsRestored, imeBoundsInit);
+                }
+            }
+        }
+    }
+
     /** Verify if {@link InputMethodService#isUiContext()} returns {@code true}. */
     @Test
     public void testIsUiContext() throws Exception {
@@ -1131,4 +1304,13 @@
             return this;
         }
     }
+
+    private static WindowLayoutInfoParcelable verifyReceivedWindowLayout(ImeEventStream stream)
+            throws TimeoutException {
+        WindowLayoutInfoParcelable received = expectEvent(stream,
+                event -> "getWindowLayoutInfo".equals(event.getEventName()),
+                TIMEOUT).getArguments().getParcelable("WindowLayoutInfo",
+                WindowLayoutInfoParcelable.class);
+        return received;
+    }
 }
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java b/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java
index b4c5c83..48d5946 100644
--- a/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java
@@ -94,24 +94,22 @@
                             mode[0] = mAom.noteOpNoThrow(
                                     OPSTR_FINE_LOCATION, ai.uid, ai.packageName);
                             isProvider[0] = mLm.isProviderPackage(null, pi.packageName, null);
-                            if (mode[0] == MODE_ALLOWED && !ignoreList.containsAll(pi.packageName)
-                                    && !isProvider[0]) {
-                                bypassedNoteOps.add(pi.packageName);
-                            }
                         });
+                        if (mode[0] == MODE_ALLOWED && !ignoreList.containsAll(pi.packageName)
+                                && !isProvider[0]) {
+                            bypassedNoteOps.add(pi.packageName);
+                        }
 
 
                         mode[0] = MODE_ALLOWED;
                         runWithShellPermissionIdentity(() -> {
                             mode[0] = mAom
                                     .checkOpNoThrow(OPSTR_FINE_LOCATION, ai.uid, ai.packageName);
-                            isProvider[0] = mLm.isProviderPackage(null, pi.packageName, null);
-                            if (mode[0] == MODE_ALLOWED && !ignoreList.includes(pi.packageName)
-                                    && !isProvider[0]) {
-                                bypassedCheckOps.add(pi.packageName);
-                            }
                         });
-
+                        if (mode[0] == MODE_ALLOWED && !ignoreList.includes(pi.packageName)
+                                && !isProvider[0]) {
+                            bypassedCheckOps.add(pi.packageName);
+                        }
                     }
                 }
 
diff --git a/tests/quicksettings/Android.bp b/tests/quicksettings/Android.bp
index 7246788..df7b7dc 100644
--- a/tests/quicksettings/Android.bp
+++ b/tests/quicksettings/Android.bp
@@ -30,6 +30,7 @@
         "androidx.test.ext.junit",
         "androidx.test.uiautomator_uiautomator",
         "compatibility-device-util-axt",
+        "SystemUI-proto",
      ],
 
     libs: [
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
index 8ef5807..3030546 100644
--- a/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
+++ b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
@@ -22,9 +22,11 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
 import android.service.quicksettings.TileService;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
@@ -35,11 +37,18 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.qs.nano.QsTileState;
+import com.android.systemui.util.nano.ComponentNameProto;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
 import java.io.IOException;
 
 @RunWith(AndroidJUnit4.class)
@@ -52,8 +61,8 @@
     protected abstract void waitForListening(boolean state) throws InterruptedException;
     protected Context mContext;
 
-    final static String DUMP_COMMAND =
-            "dumpsys activity service com.android.systemui/.SystemUIService QSTileHost";
+    static final String PROTO_DUMP_COMMAND =
+            "dumpsys statusbar QSTileHost --proto";
 
     // Time between checks for state we expect.
     protected static final long CHECK_DELAY = 250;
@@ -138,4 +147,46 @@
         }
         return null;
     }
+
+    protected final QsTileState findTileState() {
+        QsTileState[] tiles = getTilesStates();
+        for (int i = 0; i < tiles.length; i++) {
+            QsTileState tile = tiles[i];
+            if (tile.hasComponentName()) {
+                ComponentNameProto componentName = tile.getComponentName();
+                ComponentName c = ComponentName.createRelative(
+                        componentName.packageName, componentName.className);
+                if (c.flattenToString().equals(getComponentName())) {
+                    return tile;
+                }
+            }
+        }
+        return null;
+    }
+
+    private QsTileState[] getTilesStates() {
+        try {
+            return SystemUIProtoDump.parseFrom(getProtoDump()).tiles;
+        } catch (InvalidProtocolBufferNanoException e) {
+            throw new IllegalStateException("Couldn't parse proto dump", e);
+        }
+    }
+
+    private byte[] getProtoDump() {
+        try {
+            ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
+                    .getUiAutomation().executeShellCommand(PROTO_DUMP_COMMAND);
+            byte[] buf = new byte[512];
+            int bytesRead;
+            FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+            ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+            while ((bytesRead = fis.read(buf)) != -1) {
+                stdout.write(buf, 0, bytesRead);
+            }
+            fis.close();
+            return stdout.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
index 0f6d56e..67a1f40 100644
--- a/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
+++ b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
@@ -17,12 +17,15 @@
 package android.quicksettings.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.service.quicksettings.Tile;
 import android.service.quicksettings.TileService;
 
+import com.android.systemui.qs.nano.QsTileState;
+
 import org.junit.Test;
 
 public class BooleanTileServiceTest extends BaseTileServiceTest {
@@ -39,12 +42,9 @@
     public void testTileInDumpAndHasBooleanState() throws Exception {
         initializeAndListen();
 
-        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
-        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        final String line = findLine(dumpLines, tileLabel);
-        assertNotNull(line);
-        assertTrue(line.trim().startsWith("BooleanState"));
+        final QsTileState tileState = findTileState();
+        assertNotNull(tileState);
+        assertTrue(tileState.hasBooleanState());
     }
 
     @Test
@@ -58,13 +58,10 @@
     public void testValueTracksState() throws Exception {
         initializeAndListen();
 
-        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
-        String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        String line = findLine(dumpLines, tileLabel);
+        QsTileState tileState = findTileState();
 
         // Tile starts inactive
-        assertTrue(line.contains("value=false"));
+        assertFalse(tileState.getBooleanState());
 
         ((ToggleableTestTileService) mTileService).toggleState();
 
@@ -76,10 +73,9 @@
 
         assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
 
-        dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        line = findLine(dumpLines, tileLabel);
+        tileState = findTileState();
 
-        assertTrue(line.contains("value=true"));
+        assertTrue(tileState.getBooleanState());
     }
 
     @Override
diff --git a/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
index 2ccab0c..64b6ce4 100644
--- a/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
+++ b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
@@ -27,6 +27,8 @@
 import android.service.quicksettings.Tile;
 import android.service.quicksettings.TileService;
 
+import com.android.systemui.qs.nano.QsTileState;
+
 import org.junit.Test;
 
 public class TileServiceTest extends BaseTileServiceTest {
@@ -115,15 +117,32 @@
     }
 
     @Test
-    public void testTileInDumpAndHasState() throws Exception {
+    public void testTileInDumpAndHasNonBooleanState() throws Exception {
         initializeAndListen();
+        final QsTileState tileState = findTileState();
+        assertNotNull(tileState);
+        assertFalse(tileState.hasBooleanState());
+    }
 
-        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+    @Test
+    public void testTileInDumpAndHasCorrectState() throws Exception {
+        initializeAndListen();
+        CharSequence label = "test_label";
+        CharSequence subtitle = "test_subtitle";
 
-        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        final String line = findLine(dumpLines, tileLabel);
-        assertNotNull(line);
-        assertTrue(line.trim().startsWith("State")); // Not BooleanState
+        Tile tile = mTileService.getQsTile();
+        tile.setState(Tile.STATE_ACTIVE);
+        tile.setLabel(label);
+        tile.setSubtitle(subtitle);
+        tile.updateTile();
+
+        Thread.sleep(200);
+
+        final QsTileState tileState = findTileState();
+        assertNotNull(tileState);
+        assertEquals(Tile.STATE_ACTIVE,  tileState.state);
+        assertEquals(label,  tileState.getLabel());
+        assertEquals(subtitle,  tileState.getSecondaryLabel());
     }
 
     private void clickTile(String componentName) throws Exception {
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
index 291d9cc..ecc0940 100644
--- a/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
@@ -277,6 +277,13 @@
         assertOpDisallowed(cameraOp);
     }
 
+    @Test
+    public void testOpVibrateOnSuspend() throws Exception {
+        final int vibrateOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_VIBRATE);
+        SuspendTestUtils.suspend(null, null, null);
+        assertOpDisallowed(vibrateOp);
+    }
+
     @After
     public void tearDown() throws Exception {
         if (mTestAppInterface != null) {
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index 5d4e7f5..431e0c9 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -33,6 +33,9 @@
         <option name="test-file-name" value="CtsUsageStatsTestAssistApp.apk" />
         <option name="test-file-name" value="CtsUsageStatsTestExactAlarmApp.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.usage.cts" />
         <option name="runtime-hint" value="1m47s" />
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index e906720..d33758f 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -550,6 +550,10 @@
         Map<String,UsageStats> events = mUsageStatsManager.queryAndAggregateUsageStats(
                 startTime, endTime);
         UsageStats stats = events.get(mTargetPackage);
+        if (stats == null) {
+            fail("Querying UsageStats for " + mTargetPackage + " returned empty; list of packages "
+                 + "with events: " + Arrays.toString(events.keySet().toArray(new String[0])));
+        }
         int startingCount = stats.getAppLaunchCount();
         // Launch count is updated by UsageStatsService depending on last background package.
         // When running this test on single screen device (where tasks are launched in the same
diff --git a/tests/tests/bluetooth/AndroidTest.xml b/tests/tests/bluetooth/AndroidTest.xml
index 9a3075b..7b42874 100644
--- a/tests/tests/bluetooth/AndroidTest.xml
+++ b/tests/tests/bluetooth/AndroidTest.xml
@@ -25,6 +25,12 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsBluetoothTestCases.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="cmd bluetooth_manager enable" />
+        <option name="run-command" value="cmd bluetooth_manager wait-for-state:STATE_ON" />
+        <option name="teardown-command" value="cmd bluetooth_manager disable" />
+        <option name="teardown-command" value="cmd bluetooth_manager wait-for-state:STATE_OFF" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.bluetooth.cts" />
         <option name="runtime-hint" value="1m11s" />
@@ -33,6 +39,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.google.android.bluetooth" />
+        <option name="mainline-module-package-name" value="com.android.btservices" />
+        <option name="mainline-module-package-name" value="com.google.android.btservices" />
     </object>
 </configuration>
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
index f9531fb..22d3447 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth.cts;
 
+import static org.junit.Assert.assertThrows;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
@@ -23,6 +25,7 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.BluetoothManager;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -33,6 +36,7 @@
  * Other tests that run with real bluetooth connections are located in CtsVerifier.
  */
 public class BasicBluetoothGattTest extends AndroidTestCase {
+    private static final String TAG = BasicBluetoothGattTest.class.getSimpleName();
 
     private BluetoothAdapter mBluetoothAdapter;
     private BluetoothDevice mBluetoothDevice;
@@ -53,6 +57,17 @@
         mBluetoothDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
         mBluetoothGatt = mBluetoothDevice.connectGatt(
                 mContext, /*autoConnect=*/ true, new BluetoothGattCallback() {});
+        if (mBluetoothGatt == null) {
+            try {
+                Thread.sleep(500); // Bt is not binded yet. Wait and retry
+            } catch (InterruptedException e) {
+                Log.e(TAG, "delay connectGatt interrupted");
+            }
+            mBluetoothGatt = mBluetoothDevice.connectGatt(
+                    mContext, /*autoConnect=*/ true, new BluetoothGattCallback() {});
+        }
+        assertNotNull(mBluetoothGatt);
+
     }
 
     @Override
@@ -61,8 +76,9 @@
             // mBluetoothAdapter == null.
             return;
         }
-        mBluetoothGatt.disconnect();
-        assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+        if (mBluetoothGatt != null) {
+            mBluetoothGatt.disconnect();
+        }
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
             .dropShellPermissionIdentity();
     }
@@ -83,11 +99,7 @@
             return;
         }
 
-        try {
-            mBluetoothGatt.connect();
-        } catch (Exception e) {
-            fail("Exception caught from connect(): " + e.toString());
-        }
+        mBluetoothGatt.connect();
     }
 
     public void testSetPreferredPhy() throws Exception {
@@ -95,47 +107,34 @@
             return;
         }
 
-        try {
-            mBluetoothGatt.setPreferredPhy(BluetoothDevice.PHY_LE_1M, BluetoothDevice.PHY_LE_1M,
-                    BluetoothDevice.PHY_OPTION_NO_PREFERRED);
-        } catch (Exception e) {
-            fail("Exception caught from setPreferredPhy(): " + e.toString());
-        }
+        mBluetoothGatt.setPreferredPhy(BluetoothDevice.PHY_LE_1M, BluetoothDevice.PHY_LE_1M,
+                BluetoothDevice.PHY_OPTION_NO_PREFERRED);
     }
 
     public void testGetConnectedDevices() {
         if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
-        try {
-            mBluetoothGatt.getConnectedDevices();
-            fail("Should throw UnsupportedOperationException!");
-        } catch (UnsupportedOperationException ex) {
-            // Expected
-        }
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothGatt.getConnectedDevices());
     }
 
     public void testGetConnectionState() {
         if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
-        try {
-            mBluetoothGatt.getConnectionState(null);
-            fail("Should throw UnsupportedOperationException!");
-        } catch (UnsupportedOperationException ex) {
-            // Expected
-        }
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothGatt.getConnectionState(null));
     }
 
     public void testGetDevicesMatchingConnectionStates() {
         if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
-        try {
-            mBluetoothGatt.getDevicesMatchingConnectionStates(null);
-            fail("Should throw UnsupportedOperationException!");
-        } catch (UnsupportedOperationException ex) {
-            // Expected
-        }
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothGatt.getDevicesMatchingConnectionStates(null));
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
index 7a20e9b..a653bc5 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
@@ -48,8 +48,8 @@
     private BluetoothA2dpSink mBluetoothA2dpSink;
     private boolean mIsA2dpSinkSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -68,8 +68,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothA2dpSink = null;
 
@@ -88,14 +88,22 @@
             mBluetoothA2dpSink = null;
             mIsProfileReady = false;
         }
-        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mUiAutomation.dropShellPermissionIdentity();
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsA2dpSinkSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dpSink);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsA2dpSinkSupported)) return;
 
@@ -193,11 +201,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -207,28 +215,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothA2dpSinkServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
index 515f0ef..1d3c00a 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
@@ -49,8 +49,8 @@
     private BluetoothA2dp mBluetoothA2dp;
     private boolean mIsA2dpSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private ReentrantLock mProfileConnectionlock;
+    private Condition mConditionProfileConnection;
 
     @Override
     public void setUp() throws Exception {
@@ -69,8 +69,9 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+
         mIsProfileReady = false;
         mBluetoothA2dp = null;
 
@@ -89,13 +90,22 @@
             mBluetoothA2dp = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
         mUiAutomation.dropShellPermissionIdentity();
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.A2DP, mBluetoothA2dp);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsA2dpSupported)) return;
 
@@ -332,11 +342,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -346,28 +356,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothA2dpServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothA2dp = (BluetoothA2dp) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
index 44f34f6..395505f 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
@@ -89,8 +89,6 @@
     @Override
     public void tearDown() throws Exception {
         if (mHasBluetooth) {
-            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
             mUiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -366,20 +364,20 @@
             return;
         }
 
-        Duration minute = Duration.ofMinutes(1);
+        Duration minutes = Duration.ofMinutes(2);
 
         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
         assertEquals(null, mAdapter.getDiscoverableTimeout());
         assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
-                mAdapter.setDiscoverableTimeout(minute));
+                mAdapter.setDiscoverableTimeout(minutes));
 
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
         assertThrows(IllegalArgumentException.class, () -> mAdapter.setDiscoverableTimeout(
                 Duration.ofDays(25000)));
         assertEquals(BluetoothStatusCodes.SUCCESS,
-                mAdapter.setDiscoverableTimeout(minute));
-        assertEquals(minute, mAdapter.getDiscoverableTimeout());
+                mAdapter.setDiscoverableTimeout(minutes));
+        assertEquals(minutes, mAdapter.getDiscoverableTimeout());
     }
 
     public void test_getConnectionState() {
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
index f6336bf..4c641ac 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
@@ -60,7 +60,6 @@
         super.tearDown();
         if (!mHasBluetooth) return;
 
-        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
         mAdapter = null;
         mUiAutomation.dropShellPermissionIdentity();
     }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
index 50a8c46..bb4c2c1 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
@@ -21,23 +21,19 @@
 
 import static org.junit.Assert.assertThrows;
 
-import android.app.UiAutomation;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.BluetoothUuid;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.os.Build;
 import android.os.ParcelUuid;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.compatibility.common.util.ApiLevelUtil;
 
 import java.util.List;
@@ -59,8 +55,8 @@
     private BluetoothCsipSetCoordinator mBluetoothCsipSetCoordinator;
     private boolean mIsCsipSetCoordinatorSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
     private boolean mGroupLockCallbackCalled;
     private TestCallback mTestCallback;
     private Executor mTestExecutor;
@@ -94,8 +90,8 @@
             mAdapter = manager.getAdapter();
             assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-            mProfileConnectedlock = new ReentrantLock();
-            mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+            mProfileConnectionlock = new ReentrantLock();
+            mConditionProfileConnection = mProfileConnectionlock.newCondition();
             mIsProfileReady = false;
             mBluetoothCsipSetCoordinator = null;
 
@@ -134,14 +130,24 @@
                 mTestCallback = null;
                 mTestExecutor = null;
             }
-            if (mAdapter != null ) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-                mAdapter = null;
-            }
+            mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
     }
 
+    public void testCloseProfileProxy() {
+        if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothCsipSetCoordinator);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(
+                BluetoothProfile.CSIP_SET_COORDINATOR, mBluetoothCsipSetCoordinator);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void testGetConnectedDevices() {
         if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
 
@@ -265,11 +271,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -279,28 +285,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothCsipServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothCsipSetCoordinator = (BluetoothCsipSetCoordinator) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
index a3ea455..4459cbc 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
@@ -76,7 +76,6 @@
     public void tearDown() throws Exception {
         super.tearDown();
         if (mHasBluetooth && mHasCompanionDevice) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
             mAdapter = null;
             mUiAutomation.dropShellPermissionIdentity();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
index f247d5e..4ee4038 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
@@ -122,8 +122,6 @@
     public void tearDown() throws Exception {
         super.tearDown();
         if (mHasBluetooth) {
-            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
             mAdapter = null;
             mBluetoothDevice = null;
             mBluetoothGattService = null;
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
index 6c9959a..a859e73 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
@@ -70,7 +70,6 @@
                 mBluetoothGattServer.close();
                 mBluetoothGattServer = null;
             }
-            assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
             mBluetoothAdapter = null;
             mUIAutomation.dropShellPermissionIdentity();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
index fb5f241..adebccb 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
@@ -69,8 +70,8 @@
     private BluetoothHapClient mBluetoothHapClient;
     private boolean mIsHapClientSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     private boolean mOnPresetSelected = false;
     private boolean mOnPresetSelectionFailed = false;
@@ -107,8 +108,8 @@
         mAdapter = TestUtils.getBluetoothAdapterOrDie();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothHapClient = null;
 
@@ -126,14 +127,26 @@
             mBluetoothHapClient = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            mAdapter = null;
-        }
+        mAdapter = null;
         TestUtils.dropPermissionAsShellUid();
     }
 
     @Test
+    public void test_closeProfileProxy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mBluetoothHapClient);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
+    @Test
     public void testGetConnectedDevices() {
         if (shouldSkipTest()) {
             return;
@@ -466,11 +479,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -480,28 +493,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothHapClientServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothHapClient = (BluetoothHapClient) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
index 80f4bbd..2f65320 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
@@ -77,9 +77,6 @@
         if (!(mHasBluetooth && mIsHapSupported)) {
             return;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
         TestUtils.dropPermissionAsShellUid();
     }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java
index 69b5747..bac21ae 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java
@@ -49,8 +49,8 @@
     private BluetoothHeadsetClient mBluetoothHeadsetClient;
     private boolean mIsHeadsetClientSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -69,8 +69,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothHeadsetClient = null;
 
@@ -90,12 +90,21 @@
             mBluetoothHeadsetClient = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsHeadsetClientSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadsetClient);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mBluetoothHeadsetClient);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsHeadsetClientSupported)) return;
 
@@ -219,11 +228,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -233,28 +242,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothHeadsetClientServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
index cb2c2b4..cf683b4 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
@@ -49,8 +49,8 @@
     private BluetoothHeadset mBluetoothHeadset;
     private boolean mIsHeadsetSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -69,8 +69,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothHeadset = null;
 
@@ -89,13 +89,22 @@
             mBluetoothHeadset = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
         mUiAutomation.dropShellPermissionIdentity();
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsHeadsetSupported)) return;
 
@@ -376,11 +385,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -390,28 +399,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothHeadsetServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothHeadset = (BluetoothHeadset) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
index d94deab..030ee8d 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
@@ -59,8 +59,8 @@
     private boolean mIsProfileReady;
     private BluetoothAdapter mAdapter;
     private UiAutomation mUiAutomation;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
     private BluetoothHidDevice mBluetoothHidDevice;
 
 
@@ -81,8 +81,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothHidDevice = null;
 
@@ -101,14 +101,22 @@
             mBluetoothHidDevice = null;
             mIsProfileReady = false;
         }
-        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
         mUiAutomation.dropShellPermissionIdentity();
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsHidSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHidDevice);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.HID_DEVICE, mBluetoothHidDevice);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getDevicesMatchingConnectionStates() {
         if (!(mHasBluetooth && mIsHidSupported)) return;
 
@@ -206,13 +214,12 @@
                 testDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
     }
 
-
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -222,28 +229,54 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothHidServiceListener implements
             BluetoothProfile.ServiceListener {
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothHidDevice = (BluetoothHidDevice) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
-
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java
index 5c2e86e..c4fe6b6 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java
@@ -50,8 +50,8 @@
     private UiAutomation mUiAutomation;
 
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -71,8 +71,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mHidHost = null;
 
@@ -90,12 +90,21 @@
             mHidHost = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsHidHostSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mHidHost);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.HID_HOST, mHidHost);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsHidHostSupported)) return;
 
@@ -202,11 +211,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -216,28 +225,54 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothHidHostListener implements BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             try {
                 mHidHost = (BluetoothHidHost) proxy;
                 mIsProfileReady = true;
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
-            // Do nothing
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
index e7f59b8..6cf572b 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
@@ -73,7 +73,6 @@
             mAdvertiser.stopAdvertisingSet(mCallback);
             assertTrue(mCallback.mAdvertisingSetStoppedLatch.await(TIMEOUT_MS,
                     TimeUnit.MILLISECONDS));
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
             mAdvertiser = null;
             mAdapter = null;
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
index b40b52d..21ad04f 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
@@ -93,9 +93,6 @@
     @After
     public void tearDown() {
         if (mHasBluetooth) {
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
index ddb6bca..0e156cf 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
@@ -101,9 +101,6 @@
     @After
     public void tearDown() {
         if (mHasBluetooth) {
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
index 3df5b1f..8e1905c 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
@@ -54,8 +54,8 @@
     private BluetoothLeAudio mBluetoothLeAudio;
     private boolean mIsLeAudioSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
     private Executor mTestExecutor;
     private TestCallback mTestCallback;
     private boolean mCodecConfigChangedCalled;
@@ -124,8 +124,8 @@
             mAdapter = manager.getAdapter();
             assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-            mProfileConnectedlock = new ReentrantLock();
-            mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+            mProfileConnectionlock = new ReentrantLock();
+            mConditionProfileConnection = mProfileConnectionlock.newCondition();
             mIsProfileReady = false;
             mBluetoothLeAudio = null;
 
@@ -152,13 +152,22 @@
             mBluetoothLeAudio = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         TestUtils.dropPermissionAsShellUid();
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, mBluetoothLeAudio);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsLeAudioSupported)) return;
 
@@ -360,11 +369,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -374,28 +383,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothLeAudioServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothLeAudio = (BluetoothLeAudio) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
index 6138f38..09df2bb 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
@@ -98,8 +98,8 @@
     private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant;
     private boolean mIsBroadcastAssistantSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedLock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Mock
     BluetoothLeBroadcastAssistant.Callback mCallbacks;
@@ -120,8 +120,8 @@
         mAdapter = TestUtils.getBluetoothAdapterOrDie();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedLock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedLock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothLeBroadcastAssistant = null;
 
@@ -147,15 +147,28 @@
                 mBluetoothLeBroadcastAssistant = null;
                 mIsProfileReady = false;
             }
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
     }
 
     @Test
+    public void testCloseProfileProxy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(
+                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mBluetoothLeBroadcastAssistant);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
+    @Test
     public void testAddSource() {
         if (shouldSkipTest()) {
             return;
@@ -541,11 +554,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedLock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -555,27 +568,54 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedLock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class ServiceListener implements BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedLock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedLock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
index e4d0577..424a64e 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
@@ -90,9 +90,6 @@
     @After
     public void tearDown() {
         if (mHasBluetooth) {
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
index 7ff121a..5edeb38 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
@@ -114,9 +114,6 @@
     @After
     public void tearDown() {
         if (mHasBluetooth) {
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
index 8f9b0f7..fad215d 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
@@ -105,9 +105,6 @@
     @After
     public void tearDown() {
         if (mHasBluetooth) {
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
index 7279ef6..c87a95e 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
@@ -104,9 +104,6 @@
     @After
     public void tearDown() {
         if (mHasBluetooth) {
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
index e70d6ba..7b72649 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
@@ -93,8 +93,8 @@
     private BluetoothLeBroadcast mBluetoothLeBroadcast;
     private boolean mIsLeBroadcastSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     private boolean mOnBroadcastStartedCalled = false;
     private boolean mOnBroadcastStartFailedCalled = false;
@@ -167,8 +167,8 @@
         mAdapter = TestUtils.getBluetoothAdapterOrDie();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothLeBroadcast = null;
 
@@ -198,15 +198,27 @@
                 mBluetoothLeBroadcast = null;
                 mIsProfileReady = false;
             }
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
     }
 
     @Test
+    public void testCloseProfileProxy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST, mBluetoothLeBroadcast);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
+    @Test
     public void testGetConnectedDevices() {
         if (shouldSkipTest()) {
             return;
@@ -552,11 +564,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -566,28 +578,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class ServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothLeBroadcast = (BluetoothLeBroadcast) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index 2278e86..f63e02e 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -102,7 +102,6 @@
         if (!mLocationOn) {
             TestUtils.disableLocation(getContext());
         }
-        assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .dropShellPermissionIdentity();
     }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java
index 4a6cd06..34f0f52 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java
@@ -62,8 +62,8 @@
     private BluetoothMapClient mBluetoothMapClient;
     private boolean mIsMapClientSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -83,8 +83,8 @@
             mAdapter = manager.getAdapter();
             assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-            mProfileConnectedlock = new ReentrantLock();
-            mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+            mProfileConnectionlock = new ReentrantLock();
+            mConditionProfileConnection = mProfileConnectionlock.newCondition();
             mIsProfileReady = false;
             mBluetoothMapClient = null;
 
@@ -105,12 +105,21 @@
             mBluetoothMapClient = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsMapClientSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothMapClient);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsMapClientSupported)) return;
 
@@ -225,11 +234,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -239,28 +248,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothMapClientServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothMapClient = (BluetoothMapClient) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java
index 6a7fe34..0e5da68 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java
@@ -51,8 +51,8 @@
     private BluetoothMap mBluetoothMap;
     private boolean mIsMapSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -72,8 +72,8 @@
             mAdapter = manager.getAdapter();
             assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-            mProfileConnectedlock = new ReentrantLock();
-            mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+            mProfileConnectionlock = new ReentrantLock();
+            mConditionProfileConnection = mProfileConnectionlock.newCondition();
             mIsProfileReady = false;
             mBluetoothMap = null;
 
@@ -93,12 +93,21 @@
             mBluetoothMap = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsMapSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothMap);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.MAP, mBluetoothMap);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsMapSupported)) return;
 
@@ -186,11 +195,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -200,28 +209,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothMapServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             try {
                 mBluetoothMap = (BluetoothMap) proxy;
                 mIsProfileReady = true;
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java
index 733d9f3..aa4ca29 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java
@@ -46,8 +46,8 @@
     private BluetoothPan mBluetoothPan;
     private boolean mIsPanSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -66,8 +66,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothPan = null;
 
@@ -86,13 +86,22 @@
             mBluetoothPan = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
         TestUtils.dropPermissionAsShellUid();
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsPanSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothPan);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothPan);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsPanSupported)) return;
 
@@ -176,11 +185,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -190,27 +199,54 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothPanServiceListener implements BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothPan = (BluetoothPan) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java
index 7b3df4e..dcdd677 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java
@@ -48,8 +48,8 @@
     private BluetoothPbapClient mBluetoothPbapClient;
     private boolean mIsPbapClientSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -68,8 +68,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothPbapClient = null;
 
@@ -89,12 +89,21 @@
             mBluetoothPbapClient = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsPbapClientSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothPbapClient);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT, mBluetoothPbapClient);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsPbapClientSupported)) return;
 
@@ -182,11 +191,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -196,28 +205,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothPbapClientServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothPbapClient = (BluetoothPbapClient) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java
index 382a292..05b6e91 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java
@@ -49,8 +49,8 @@
     private BluetoothPbap mBluetoothPbap;
     private boolean mIsPbapSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     @Override
     public void setUp() throws Exception {
@@ -70,8 +70,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothPbap = null;
 
@@ -91,12 +91,21 @@
             mBluetoothPbap = null;
             mIsProfileReady = false;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsPbapSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothPbap);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.PBAP, mBluetoothPbap);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsPbapSupported)) return;
 
@@ -165,11 +174,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -179,28 +188,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothPbapServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             try {
                 mBluetoothPbap = (BluetoothPbap) proxy;
                 mIsProfileReady = true;
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
index ad5cfff..6da4b9a 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
@@ -48,8 +48,8 @@
 
     private BluetoothSap mBluetoothSap;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
 
     private boolean mIsSapSupported;
 
@@ -70,8 +70,8 @@
         mAdapter = getContext().getSystemService(BluetoothManager.class).getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothSap = null;
 
@@ -88,15 +88,23 @@
                 mBluetoothSap = null;
                 mIsProfileReady = false;
             }
-            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            }
             mUiAutomation.dropShellPermissionIdentity();
             mAdapter = null;
         }
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mHasBluetooth && mIsSapSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothSap);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.SAP, mBluetoothSap);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     @MediumTest
     public void test_getConnectedDevices() {
         if (!mHasBluetooth || !mIsSapSupported) return;
@@ -161,11 +169,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -175,27 +183,54 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothSapServiceListener implements BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothSap = (BluetoothSap) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
index 134e4bd..ba3cc71 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
@@ -60,7 +60,6 @@
             if (mHasBluetooth && mBluetoothServerSocket != null) {
                 mBluetoothServerSocket.close();
             }
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
             mAdapter = null;
             mBluetoothServerSocket = null;
             mUiAutomation.dropShellPermissionIdentity();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
index 26237c1..09c4308 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
@@ -47,8 +47,8 @@
     private BluetoothVolumeControl mBluetoothVolumeControl;
     private boolean mIsVolumeControlSupported;
     private boolean mIsProfileReady;
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
     private boolean mVolumeOffsetChangedCallbackCalled;
     private TestCallback mTestCallback;
     private Executor mTestExecutor;
@@ -78,8 +78,8 @@
         mAdapter = manager.getAdapter();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mBluetoothVolumeControl = null;
 
@@ -117,14 +117,23 @@
                 mTestCallback = null;
                 mTestExecutor = null;
             }
-            if (mAdapter != null) {
-                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-                mAdapter = null;
-            }
+            mAdapter = null;
             TestUtils.dropPermissionAsShellUid();
         }
     }
 
+    public void testCloseProfileProxy() {
+        if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothVolumeControl);
+        assertTrue(mIsProfileReady);
+
+        mAdapter.closeProfileProxy(BluetoothProfile.VOLUME_CONTROL, mBluetoothVolumeControl);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     public void testGetConnectedDevices() {
         if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
 
@@ -290,11 +299,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                         PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -304,28 +313,55 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class BluetoothVolumeControlServiceListener implements
             BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mBluetoothVolumeControl = (BluetoothVolumeControl) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
+            mProfileConnectionlock.lock();
+            mIsProfileReady = false;
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
index 2ed393f..536d32b 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
@@ -69,8 +69,8 @@
     private BroadcastReceiver mIntentReceiver;
     private UiAutomation mUiAutomation;;
 
-    private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private Condition mConditionProfileConnection;
+    private ReentrantLock mProfileConnectionlock;
     private boolean mIsProfileReady;
 
     private static List<Integer> mValidConnectionStates = new ArrayList<Integer>(
@@ -94,8 +94,8 @@
         mBluetoothAdapter = manager.getAdapter();
 
         assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+        mProfileConnectionlock = new ReentrantLock();
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
         mIsProfileReady = false;
         mService = null;
         mBluetoothAdapter.getProfileProxy(getContext(), new HearingAidsServiceListener(),
@@ -107,12 +107,21 @@
         if (!(mIsBleSupported && mIsHearingAidSupported)) {
             return;
         }
-        if (mBluetoothAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
-        }
         mUiAutomation.dropShellPermissionIdentity();
     }
 
+    public void test_closeProfileProxy() {
+        if (!(mIsBleSupported && mIsHearingAidSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mService);
+        assertTrue(mIsProfileReady);
+
+        mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEARING_AID, mService);
+        assertTrue(waitForProfileDisconnect());
+        assertFalse(mIsProfileReady);
+    }
+
     /**
      * Basic test case to make sure that Hearing Aid Profile Proxy can connect.
      */
@@ -275,11 +284,11 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectionlock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
-                if (!mConditionProfileIsConnected.await(
+                if (!mConditionProfileConnection.await(
                     PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                     // Timeout
                     Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -289,30 +298,54 @@
         } catch(InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectionlock.unlock();
         }
         return mIsProfileReady;
     }
 
+    private boolean waitForProfileDisconnect() {
+        mConditionProfileConnection = mProfileConnectionlock.newCondition();
+        mProfileConnectionlock.lock();
+        try {
+            while (mIsProfileReady) {
+                if (!mConditionProfileConnection.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+        } finally {
+            mProfileConnectionlock.unlock();
+        }
+        return !mIsProfileReady;
+    }
+
     private final class HearingAidsServiceListener
             implements BluetoothProfile.ServiceListener {
 
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mService = (BluetoothHearingAid) proxy;
             mIsProfileReady = true;
             try {
-                mConditionProfileIsConnected.signal();
+                mConditionProfileConnection.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectionlock.unlock();
             }
         }
 
         public void onServiceDisconnected(int profile) {
-            mProfileConnectedlock.lock();
+            mProfileConnectionlock.lock();
             mIsProfileReady = false;
             mService = null;
-            mProfileConnectedlock.unlock();
+            try {
+                mConditionProfileConnection.signal();
+            } finally {
+                mProfileConnectionlock.unlock();
+            }
         }
     }
 
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
index 62d1c6a..7973808 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
@@ -51,9 +51,6 @@
         if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
-        if (mAdapter != null) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
         mAdapter = null;
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
             .dropShellPermissionIdentity();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java
index 40d5c3f..88b5fb3 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java
@@ -18,11 +18,12 @@
 
 import static android.bluetooth.cts.TestUtils.assertArrayEquals;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.bluetooth.OobData;
 import android.content.pm.PackageManager;
 import android.test.AndroidTestCase;
 
-import java.util.Arrays;
 
 public class OobDataTest extends AndroidTestCase {
 
@@ -177,4 +178,59 @@
         assertArrayEquals(leTemporaryKey, leData.getLeTemporaryKey());
         assertEquals(OobData.LE_FLAG_BREDR_NOT_SUPPORTED, leData.getLeFlags());
     }
+
+    public void testToString() {
+        byte[] confirmationHash = new byte[]{0x52, 0x70, 0x49, 0x41, 0x1A, (byte) 0xB3, 0x3F, 0x5C,
+                (byte) 0xE0, (byte) 0x99, 0x37, 0x29, 0x21, 0x52, 0x65, 0x49};
+        byte[] address = new byte[]{0x12, 0x34, 0x56, 0x78, (byte) 0x8A, (byte) 0xBC, 0x0};
+
+        OobData.LeBuilder leBuilder = new OobData.LeBuilder(confirmationHash, address,
+                OobData.LE_DEVICE_ROLE_PERIPHERAL_ONLY);
+
+        String deviceNameString = "Test Device Name";
+        byte[] deviceName = deviceNameString.getBytes();
+        byte[] randomizerHash = new byte[]{(byte) 0x9E, 0x43, 0x51, 0x10, 0x70, 0x33, 0x01,
+                (byte) 0xDE, 0x00, 0x02, 0x03, 0x05, 0x09, 0x10, 0x40, 0x07};
+        byte[] leTemporaryKey = new byte[]{0x01, 0x12, 0x34, 0x56, 0x78, (byte) 0x9A, (byte) 0xBC,
+                (byte) 0xDE, (byte) 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
+
+        leBuilder
+                .setDeviceName(deviceName)
+                .setRandomizerHash(randomizerHash)
+                .setLeTemporaryKey(leTemporaryKey)
+                .setLeFlags(OobData.LE_FLAG_BREDR_NOT_SUPPORTED);
+        OobData leData = leBuilder.build();
+
+        String expected = "OobData: \n\t"
+                + "Device Address With Type: "
+                + toHexString(leData.getDeviceAddressWithType()) + "\n\t"
+                + "Confirmation: " + toHexString(leData.getConfirmationHash()) + "\n\t"
+                + "Randomizer: " + toHexString(leData.getRandomizerHash()) + "\n\t"
+                + "Device Name: " + toHexString(leData.getDeviceName()) + "\n\t"
+                + "OobData Length: " + toHexString(leData.getClassicLength()) + "\n\t"
+                + "Class of Device: " + toHexString(leData.getClassOfDevice()) + "\n\t"
+                + "LE Device Role: " + toHexString(leData.getLeDeviceRole()) + "\n\t"
+                + "LE Temporary Key: " + toHexString(leData.getLeTemporaryKey()) + "\n\t"
+                + "LE Appearance: " + toHexString(leData.getLeAppearance()) + "\n\t"
+                + "LE Flags: " + toHexString(leData.getLeFlags()) + "\n\t";
+        String toString = leData.toString();
+
+        assertThat(toString).isEqualTo(expected);
+
+        int describeContents = 0;
+        assertThat(leData.describeContents()).isEqualTo(describeContents);
+    }
+
+    private String toHexString(int b) {
+        return toHexString(new byte[]{(byte) b});
+    }
+
+    private String toHexString(byte[] array) {
+        if (array == null) return "null";
+        StringBuilder builder = new StringBuilder(array.length * 2);
+        for (byte b : array) {
+            builder.append(String.format("%02x", b));
+        }
+        return builder.toString();
+    }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java
index e9a9314..bfaf557 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java
@@ -82,9 +82,6 @@
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
-        if (mHasBluetooth) {
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-        }
     }
 
     /**
diff --git a/tests/tests/car/Android.bp b/tests/tests/car/Android.bp
index 6140f7b..92351cb 100644
--- a/tests/tests/car/Android.bp
+++ b/tests/tests/car/Android.bp
@@ -32,7 +32,9 @@
     name: "CtsCarTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
+        "android.car.test.utils",
         "androidx.test.rules",
+        "android-support-v4",
         "compatibility-device-util-axt",
         "truth-prebuilt",
         "ctstestrunner-axt",
diff --git a/tests/tests/car/AndroidManifest.xml b/tests/tests/car/AndroidManifest.xml
index a0326fc..89a270d 100644
--- a/tests/tests/car/AndroidManifest.xml
+++ b/tests/tests/car/AndroidManifest.xml
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.car.permission.CAR_SPEED" />
     <uses-permission android:name="android.car.permission.CONTROL_CAR_DISPLAY_UNITS" />
     <uses-permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS" />
+    <uses-permission android:name="android.car.permission.CAR_TEST_SERVICE" />
     <uses-permission android:name="android.car.permission.READ_CAR_POWER_POLICY" />
     <uses-permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
@@ -45,6 +46,12 @@
         <activity android:name=".drivingstate.NonDistractionOptimizedActivity">
             <meta-data android:name="distractionOptimized" android:value="false"/>
         </activity>
+
+        <!-- Setting the car target version to TIRAMISU_1 to allow tests checking car target version
+             to pass, e.g.
+             CarServiceHelperServiceUpdatableTest#testSendUserLifecycleEventAndOnUserCreated,
+             CarServiceHelperServiceUpdatableTest#testSendUserLifecycleEventAndOnUserRemoved -->
+        <meta-data android:name="android.car.targetCarVersion" android:value="33:1"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/car/src/android/car/cts/AbstractCarLessTestCase.java b/tests/tests/car/src/android/car/cts/AbstractCarLessTestCase.java
new file mode 100644
index 0000000..3e9632f
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/AbstractCarLessTestCase.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cts;
+
+import android.car.test.AbstractExpectableTestCase;
+import android.car.test.ApiCheckerRule;
+
+import org.junit.Rule;
+
+/**
+ * Base class for tests that don't need to connect to a {@code android.car.Car} object.
+ *
+ * <p>Typically used to test POJO-like (Plain-Old Java Objects) classes.
+ */
+abstract class AbstractCarLessTestCase extends AbstractExpectableTestCase {
+
+    @Rule
+    public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+}
diff --git a/tests/tests/car/src/android/car/cts/ApiVersionTest.java b/tests/tests/car/src/android/car/cts/ApiVersionTest.java
new file mode 100644
index 0000000..603ad27
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/ApiVersionTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.car.ApiVersion;
+import android.car.CarVersion;
+import android.car.PlatformVersion;
+
+//TODO(b/236153976): add when supported
+//import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class ApiVersionTest {
+
+    private final ApiVersionFactory<?> mFactory;
+
+    public ApiVersionTest(ApiVersionFactory<?> factory) {
+        mFactory = factory;
+    }
+
+    @Test
+    public void testGetters() {
+        ApiVersion<?> version = version(42, 108);
+
+        assertWithMessage("%s.getMajorVersion()", version)
+                .that(version.getMajorVersion()).isEqualTo(42);
+        assertWithMessage("%s.getMinorVersion()", version)
+                .that(version.getMinorVersion()).isEqualTo(108);
+    }
+
+    @Test
+    public void testGetters_majorOnlyConstructor() {
+        ApiVersion<?> version = version(42);
+
+        assertWithMessage("%s.getMajorVersion()", version)
+                .that(version.getMajorVersion()).isEqualTo(42);
+        assertWithMessage("%s.getMinorVersion()", version)
+                .that(version.getMinorVersion()).isEqualTo(0);
+    }
+
+    @Test
+    public void testToString() {
+        String string = version(42, 108).toString();
+
+        assertWithMessage("version(42, 108).toString()").that(string).contains("major=42");
+        assertWithMessage("version(42, 108).toString()").that(string).contains("minor=108");
+        assertWithMessage("version(42, 108).toString()").that(string).doesNotContain("name=");
+    }
+
+    @Test
+    public void testAtLeast_null() {
+        assertThrows(NullPointerException.class, () -> version(42, 108).isAtLeast(null));
+    }
+
+    @Test
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void testAtLeast_major() {
+        ApiVersion version = version(42, 108);
+
+        assertWithMessage("%s.atLeast(41)", version)
+                .that(version.isAtLeast(version(41))).isTrue();
+        assertWithMessage("%s.atLeast(42)", version)
+                .that(version.isAtLeast(version(42))).isTrue();
+        assertWithMessage("%s.atLeast(43)", version)
+                .that(version.isAtLeast(version(43))).isFalse();
+    }
+
+    @Test
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void testAtLeast_majorAndMinor() {
+        ApiVersion version = version(42, 108);
+
+        assertWithMessage("%s.atLeast(41, 109)", version)
+                .that(version.isAtLeast(version(41, 109))).isTrue();
+        assertWithMessage("%s.atLeast(42, 107)", version)
+                .that(version.isAtLeast(version(42, 107))).isTrue();
+        assertWithMessage("%s.atLeast(42, 108)", version)
+                .that(version.isAtLeast(version(42, 108))).isTrue();
+
+        assertWithMessage("%s.atLeast(42, 109)", version)
+                .that(version.isAtLeast(version(42, 109))).isFalse();
+        assertWithMessage("%s.atLeast(43, 0)", version)
+                .that(version.isAtLeast(version(43, 0))).isFalse();
+    }
+
+    // TODO(b/236153976): comment back once guava is supported
+    // (then also add check for different string but same versions)
+//    @Test
+//    public void testEqualsAndHashcode() {
+//        new EqualsTester()
+//                .addEqualityGroup(version(4, 8), version(4, 8))
+//                .addEqualityGroup(version(15), version(15))
+//                .addEqualityGroup(version(16), version(16, 0))
+//                .addEqualityGroup(version(23, 0), version(23))
+//
+//                // Make sure different subclasses are different
+//                .addEqualityGroup(CarApiVersion.forMajorVersion(42),
+//                        CarApiVersion.forMajorVersion(42))
+//                .addEqualityGroup(PlatformApiVersion.forMajorVersion(42),
+//                        PlatformApiVersion.forMajorVersion(42))
+//
+//                .testEquals();
+//    }
+
+    @Test
+    public void testAtLeast_wrongTypes() {
+        @SuppressWarnings("rawtypes")
+        ApiVersion myObject = version(42);
+        @SuppressWarnings("rawtypes")
+        ApiVersion otherObject = mFactory.otherType(42);
+
+        assertThrows(IllegalArgumentException.class, () -> myObject.isAtLeast(otherObject));
+        assertThrows(IllegalArgumentException.class, () -> otherObject.isAtLeast(myObject));
+    }
+
+    private ApiVersion<?> version(int major, int minor) {
+        return mFactory.newApiVersion(major, minor);
+    }
+
+    private ApiVersion<?> version(int major) {
+        return mFactory.newApiVersion(major);
+    }
+
+    @Parameterized.Parameters
+    public static Collection<?> parameters() {
+        return Arrays.asList(
+                new Object[][] {
+                    { new CarApiVersionFactory() },
+                    { new PlatformApiVersionFactory() },
+                });
+    }
+
+    private interface ApiVersionFactory<T extends ApiVersion<T>> {
+        T newApiVersion(int majorVersion, int minorVersion);
+        T newApiVersion(int majorVersion);
+        ApiVersion<?> otherType(int majorVersion);
+    }
+
+    private static final class CarApiVersionFactory implements ApiVersionFactory<CarVersion> {
+
+        @Override
+        public CarVersion newApiVersion(int majorVersion, int minorVersion) {
+            return CarVersion.forMajorAndMinorVersions(majorVersion, minorVersion);
+        }
+
+        @Override
+        public CarVersion newApiVersion(int majorVersion) {
+            return CarVersion.forMajorVersion(majorVersion);
+        }
+
+        @Override
+        public ApiVersion<?> otherType(int majorVersion) {
+            return PlatformVersion.forMajorVersion(majorVersion);
+        }
+    }
+
+    private static final class PlatformApiVersionFactory
+            implements ApiVersionFactory<PlatformVersion> {
+
+        @Override
+        public PlatformVersion newApiVersion(int majorVersion, int minorVersion) {
+            return PlatformVersion.forMajorAndMinorVersions(majorVersion, minorVersion);
+        }
+
+        @Override
+        public PlatformVersion newApiVersion(int majorVersion) {
+            return PlatformVersion.forMajorVersion(majorVersion);
+        }
+
+        @Override
+        public ApiVersion<?> otherType(int majorVersion) {
+            return CarVersion.forMajorVersion(majorVersion);
+        }
+    }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarPerformanceManagerTest.java b/tests/tests/car/src/android/car/cts/CarPerformanceManagerTest.java
new file mode 100644
index 0000000..6a11670
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarPerformanceManagerTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cts;
+
+import static android.os.Process.getThreadPriority;
+import static android.os.Process.setThreadPriority;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.UiAutomation;
+import android.car.Car;
+import android.car.os.CarPerformanceManager;
+import android.car.os.ThreadPolicyWithPriority;
+import android.car.test.ApiCheckerRule;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SmallTest
+@AppModeFull(reason = "Instant Apps cannot get car related permissions")
+public class CarPerformanceManagerTest extends CarApiTestBase {
+
+    private UiAutomation mUiAutomation;
+    private CarPerformanceManager mCarPerformanceManager;
+    private ThreadPolicyWithPriority mOriginalPolicyWithPriority;
+
+    // TODO(b/242350638): move to super class (although it would need to call
+    // disableAnnotationsCheck()
+    @Rule
+    public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+
+    private void setThreadPriorityGotThreadPriorityVerify(ThreadPolicyWithPriority p)
+            throws Exception {
+        mCarPerformanceManager.setThreadPriority(p);
+
+        ThreadPolicyWithPriority gotP = mCarPerformanceManager.getThreadPriority();
+
+        assertThat(gotP.getPolicy()).isEqualTo(p.getPolicy());
+        assertThat(gotP.getPriority()).isEqualTo(p.getPriority());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(Car.PERMISSION_MANAGE_THREAD_PRIORITY);
+
+        mCarPerformanceManager = (CarPerformanceManager) getCar().getCarManager(
+                Car.CAR_PERFORMANCE_SERVICE);
+        assertThat(mCarPerformanceManager).isNotNull();
+
+        // TODO(b/237015981): it would be cleaner to split this logic into a separate @Before method
+        // which would be annotated with:
+        //   @TestApiRequirements(requiresApi="...", onApiViolation=IGNORE)
+        // But that would require a new rule to wrap the whole test class with
+        // adoptShellPermissionIdentity (otherwise there would be no guarantee that the new method
+        // would be called before the call to adoptShellPermissionIdentity)
+        if (mApiCheckerRule.isApiSupported("android.car.os.CarPerformanceManager#"
+                + "getThreadPriority")) {
+            mOriginalPolicyWithPriority = mCarPerformanceManager.getThreadPriority();
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mOriginalPolicyWithPriority != null) {
+            mCarPerformanceManager.setThreadPriority(mOriginalPolicyWithPriority);
+        }
+
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+            "android.car.os.CarPerformanceManager#getThreadPriority"})
+    public void testSetThreadPriorityDefault() throws Exception {
+        setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_DEFAULT, /* priority= */ 0));
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+            "android.car.os.CarPerformanceManager#getThreadPriority"})
+    public void testSetThreadPriorityFIFOMinPriority() throws Exception {
+        setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_FIFO,
+                ThreadPolicyWithPriority.PRIORITY_MIN));
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+            "android.car.os.CarPerformanceManager#getThreadPriority"})
+    public void testSetThreadPriorityFIFOMaxPriority() throws Exception {
+        setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_FIFO,
+                ThreadPolicyWithPriority.PRIORITY_MAX));
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+            "android.car.os.CarPerformanceManager#getThreadPriority"})
+    public void testSetThreadPriorityRRMinPriority() throws Exception {
+        setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_RR,
+                ThreadPolicyWithPriority.PRIORITY_MIN));
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+            "android.car.os.CarPerformanceManager#getThreadPriority"})
+    public void testSetThreadPriorityRRMaxPriority() throws Exception {
+        setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_RR,
+                ThreadPolicyWithPriority.PRIORITY_MAX));
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+            "android.car.os.CarPerformanceManager#getThreadPriority"})
+    public void testSetThreadPriorityDefaultKeepNiceValue() throws Exception {
+        int expectedNiceValue = 10;
+
+        // Resume the test scheduling policy to default policy.
+        mCarPerformanceManager.setThreadPriority(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_DEFAULT, /* priority= */ 0));
+        // Set a nice value for regular scheduling policy.
+        setThreadPriority(expectedNiceValue);
+
+        // Change the scheduling policy.
+        setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_FIFO,
+                ThreadPolicyWithPriority.PRIORITY_MIN));
+
+        // Change it back, the nice value should be resumed.
+        setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+                ThreadPolicyWithPriority.SCHED_DEFAULT, /* priority= */ 0));
+
+        assertThat(getThreadPriority(/* tid= */ 0)).isEqualTo(expectedNiceValue);
+    }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index 0c9bcc5..a1707c2 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
 
 import android.app.UiAutomation;
@@ -40,29 +41,34 @@
 import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
 import android.car.hardware.property.VehicleElectronicTollCollectionCardStatus;
 import android.car.hardware.property.VehicleElectronicTollCollectionCardType;
+import android.content.pm.PackageManager;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.RequiresDevice;
+import android.support.v4.content.ContextCompat;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
 import androidx.annotation.GuardedBy;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.CddTest;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 
 import org.junit.Assert;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @SmallTest
 @RequiresDevice
@@ -104,10 +110,47 @@
     private static final ImmutableSet<Integer> SPEED_DISPLAY_UNITS =
             ImmutableSet.<Integer>builder().add(VehicleUnit.METER_PER_SEC,
                     VehicleUnit.MILES_PER_HOUR, VehicleUnit.KILOMETERS_PER_HOUR).build();
+    private static final ImmutableSet<Integer> TURN_SIGNAL_STATES =
+            ImmutableSet.<Integer>builder().add(/*TurnSignalState.NONE=*/0,
+                    /*TurnSignalState.RIGHT=*/1, /*TurnSignalState.LEFT=*/2).build();
+    private static final ImmutableSet<Integer> VEHICLE_LIGHT_STATES =
+            ImmutableSet.<Integer>builder().add(/*VehicleLightState.OFF=*/0,
+                    /*VehicleLightState.ON=*/1, /*VehicleLightState.DAYTIME_RUNNING=*/2).build();
+    private static final ImmutableSet<Integer> VEHICLE_LIGHT_SWITCHES =
+            ImmutableSet.<Integer>builder().add(/*VehicleLightSwitch.OFF=*/0,
+                    /*VehicleLightSwitch.ON=*/1, /*VehicleLightSwitch.DAYTIME_RUNNING=*/2,
+                    /*VehicleLightSwitch.AUTOMATIC=*/256).build();
+    private static final ImmutableSet<Integer> HVAC_TEMPERATURE_DISPLAY_UNITS =
+            ImmutableSet.<Integer>builder().add(VehicleUnit.CELSIUS,
+                    VehicleUnit.FAHRENHEIT).build();
+    private static final ImmutableSet<Integer> SINGLE_HVAC_FAN_DIRECTIONS = ImmutableSet.of(
+            /*VehicleHvacFanDirection.FACE=*/0x1, /*VehicleHvacFanDirection.FLOOR=*/0x2,
+            /*VehicleHvacFanDirection.DEFROST=*/0x4);
+    private static final ImmutableSet<Integer> ALL_POSSIBLE_HVAC_FAN_DIRECTIONS =
+            generateAllPossibleHvacFanDirections();
+    private static final ImmutableSet<Integer> VEHICLE_SEAT_OCCUPANCY_STATES = ImmutableSet.of(
+            /*VehicleSeatOccupancyState.UNKNOWN=*/0, /*VehicleSeatOccupancyState.VACANT=*/1,
+            /*VehicleSeatOccupancyState.OCCUPIED=*/2);
+
     /** contains property Ids for the properties required by CDD */
     private final ArraySet<Integer> mPropertyIds = new ArraySet<>();
     private CarPropertyManager mCarPropertyManager;
 
+    private static ImmutableSet<Integer> generateAllPossibleHvacFanDirections() {
+        ImmutableSet.Builder<Integer> allPossibleFanDirectionsBuilder = ImmutableSet.builder();
+        for (int i = 1; i <= SINGLE_HVAC_FAN_DIRECTIONS.size(); i++) {
+            allPossibleFanDirectionsBuilder.addAll(Sets.combinations(SINGLE_HVAC_FAN_DIRECTIONS,
+                    i).stream().map(hvacFanDirectionCombo -> {
+                Integer possibleHvacFanDirection = 0;
+                for (Integer hvacFanDirection : hvacFanDirectionCombo) {
+                    possibleHvacFanDirection |= hvacFanDirection;
+                }
+                return possibleHvacFanDirection;
+            }).collect(Collectors.toList()));
+        }
+        return allPossibleFanDirectionsBuilder.build();
+    }
+
     private static void verifyWheelTickConfigArray(int supportedWheels, int wheelToVerify,
             int configArrayIndex, int wheelTicksToUm) {
         if ((supportedWheels & wheelToVerify) != 0) {
@@ -154,6 +197,20 @@
         }
     }
 
+    private static void adoptSystemLevelPermission(String permission, Runnable verifierRunnable) {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity(permission);
+        try {
+            assumeTrue("Unable to adopt Car Shell permission: " + permission,
+                    ContextCompat.checkSelfPermission(
+                            InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                            permission) == PackageManager.PERMISSION_GRANTED);
+            verifierRunnable.run();
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -332,6 +389,29 @@
     }
 
     @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testInfoVinIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_IDENTIFICATION, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.INFO_VIN,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+                    String.class).setCarPropertyValueVerifier(
+                        (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+                            "INFO_VIN must be 17 characters").that(
+                                (String) carPropertyValue.getValue()).hasLength(17))
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
     public void testInfoMakeIfSupported() {
         VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.INFO_MAKE,
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
@@ -443,14 +523,8 @@
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
-                Integer.class).setCarPropertyValueVerifier(
-                (carPropertyConfig, carPropertyValue) -> {
-                    Integer fuelDoorLocation = (Integer) carPropertyValue.getValue();
-                    assertWithMessage(
-                            "INFO_FUEL_DOOR_LOCATION must be a defined port location: "
-                                    + fuelDoorLocation).that(
-                            fuelDoorLocation).isIn(PORT_LOCATION_TYPES);
-                }).build().verify(mCarPropertyManager);
+                Integer.class).setPossibleCarPropertyValues(PORT_LOCATION_TYPES).build()
+                .verify(mCarPropertyManager);
     }
 
     @Test
@@ -459,14 +533,8 @@
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
-                Integer.class).setCarPropertyValueVerifier(
-                (carPropertyConfig, carPropertyValue) -> {
-                    Integer evPortLocation = (Integer) carPropertyValue.getValue();
-                    assertWithMessage(
-                            "INFO_EV_PORT_LOCATION must be a defined port location: "
-                                    + evPortLocation).that(
-                            evPortLocation).isIn(PORT_LOCATION_TYPES);
-                }).build().verify(mCarPropertyManager);
+                Integer.class).setPossibleCarPropertyValues(PORT_LOCATION_TYPES).build()
+                .verify(mCarPropertyManager);
     }
 
     @Test
@@ -496,21 +564,16 @@
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
                 VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
-                Integer.class).setCarPropertyValueVerifier(
-                (carPropertyConfig, carPropertyValue) -> {
-                    Integer driverSeat = (Integer) carPropertyValue.getValue();
-                    assertWithMessage("INFO_DRIVER_SEAT must be a defined front seat location: "
-                            + driverSeat).that(driverSeat).isIn(
-                            ImmutableSet.builder().add(VehicleAreaSeat.SEAT_UNKNOWN,
-                                    VehicleAreaSeat.SEAT_ROW_1_LEFT,
-                                    VehicleAreaSeat.SEAT_ROW_1_CENTER,
-                                    VehicleAreaSeat.SEAT_ROW_1_RIGHT).build());
-                }).setAreaIdsVerifier(areaIds -> assertWithMessage(
+                Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+                        VehicleAreaSeat.SEAT_UNKNOWN,
+                        VehicleAreaSeat.SEAT_ROW_1_LEFT,
+                        VehicleAreaSeat.SEAT_ROW_1_CENTER,
+                        VehicleAreaSeat.SEAT_ROW_1_RIGHT))
+                .setAreaIdsVerifier(areaIds -> assertWithMessage(
                 "Even though INFO_DRIVER_SEAT is VEHICLE_AREA_TYPE_SEAT, it is meant to be "
                         + "VEHICLE_AREA_TYPE_GLOBAL, so its AreaIds must contain a single 0").that(
-                areaIds).isEqualTo(
-                new int[]{VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL})).build().verify(
-                mCarPropertyManager);
+                areaIds).isEqualTo(new int[]{VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL}))
+                .build().verify(mCarPropertyManager);
     }
 
     @Test
@@ -540,20 +603,11 @@
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setCarPropertyValueVerifier(
-                (carPropertyConfig, carPropertyValue) -> {
-                    Integer electronicTollCollectionCardType =
-                            (Integer) carPropertyValue.getValue();
-                    assertWithMessage(
-                            "ELECTRONIC_TOLL_COLLECTION_CARD_TYPE value must be a valid "
-                                    + "VehicleElectronicTollCollectionCardType").that(
-                            electronicTollCollectionCardType).isIn(ImmutableSet.builder().add(
-                            VehicleElectronicTollCollectionCardType.UNKNOWN,
-                            VehicleElectronicTollCollectionCardType.
-                                    JP_ELECTRONIC_TOLL_COLLECTION_CARD,
-                            VehicleElectronicTollCollectionCardType.
-                                    JP_ELECTRONIC_TOLL_COLLECTION_CARD_V2).build());
-                }).build().verify(mCarPropertyManager);
+                Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+                    VehicleElectronicTollCollectionCardType.UNKNOWN,
+                    VehicleElectronicTollCollectionCardType.JP_ELECTRONIC_TOLL_COLLECTION_CARD,
+                    VehicleElectronicTollCollectionCardType.JP_ELECTRONIC_TOLL_COLLECTION_CARD_V2)
+        ).build().verify(mCarPropertyManager);
     }
 
     @Test
@@ -563,22 +617,15 @@
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setCarPropertyValueVerifier(
-                (carPropertyConfig, carPropertyValue) -> {
-                    Integer electronicTollCollectionCardStatus =
-                            (Integer) carPropertyValue.getValue();
-                    assertWithMessage(
-                            "ELECTRONIC_TOLL_COLLECTION_CARD_STATUS value must be a valid "
-                                    + "VehicleElectronicTollCollectionCardStatus").that(
-                            electronicTollCollectionCardStatus).isIn(ImmutableSet.builder().add(
-                            VehicleElectronicTollCollectionCardStatus.UNKNOWN,
-                            VehicleElectronicTollCollectionCardStatus.
-                                    ELECTRONIC_TOLL_COLLECTION_CARD_VALID,
-                            VehicleElectronicTollCollectionCardStatus.
-                                    ELECTRONIC_TOLL_COLLECTION_CARD_INVALID,
-                            VehicleElectronicTollCollectionCardStatus.
-                                    ELECTRONIC_TOLL_COLLECTION_CARD_NOT_INSERTED).build());
-                }).build().verify(mCarPropertyManager);
+                Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+                        VehicleElectronicTollCollectionCardStatus.UNKNOWN,
+                        VehicleElectronicTollCollectionCardStatus
+                                .ELECTRONIC_TOLL_COLLECTION_CARD_VALID,
+                        VehicleElectronicTollCollectionCardStatus
+                                .ELECTRONIC_TOLL_COLLECTION_CARD_INVALID,
+                        VehicleElectronicTollCollectionCardStatus
+                                .ELECTRONIC_TOLL_COLLECTION_CARD_NOT_INSERTED)
+        ).build().verify(mCarPropertyManager);
     }
 
     @Test
@@ -616,82 +663,381 @@
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setCarPropertyValueVerifier(
-                (carPropertyConfig, carPropertyValue) -> {
-                    Integer ignitionState = (Integer) carPropertyValue.getValue();
-                    assertWithMessage(
-                            "IGNITION_STATE must be a defined ignition state: "
-                                    + ignitionState).that(
-                            ignitionState).isIn(ImmutableSet.of(VehicleIgnitionState.UNDEFINED,
-                            VehicleIgnitionState.LOCK, VehicleIgnitionState.OFF,
-                            VehicleIgnitionState.ACC, VehicleIgnitionState.ON,
-                            VehicleIgnitionState.START));
-                }).build().verify(mCarPropertyManager);
+                Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+                        VehicleIgnitionState.UNDEFINED, VehicleIgnitionState.LOCK,
+                        VehicleIgnitionState.OFF, VehicleIgnitionState.ACC, VehicleIgnitionState.ON,
+                VehicleIgnitionState.START)).build().verify(mCarPropertyManager);
+    }
+
+    @Test
+    public void testAbsActiveIfSupported() {
+        adoptSystemLevelPermission(/* Car.PERMISSION_CAR_DYNAMICS_STATE = */
+                "android.car.permission.CAR_DYNAMICS_STATE", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ABS_ACTIVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Boolean.class).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testTractionControlActiveIfSupported() {
+        adoptSystemLevelPermission(/* Car.PERMISSION_CAR_DYNAMICS_STATE = */
+                "android.car.permission.CAR_DYNAMICS_STATE", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TRACTION_CONTROL_ACTIVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Boolean.class).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testDoorPosIfSupported() {
+        adoptSystemLevelPermission(/* Car.PERMISSION_CONTROL_CAR_DOORS = */
+                "android.car.permission.CONTROL_CAR_DOORS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DOOR_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_DOOR,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().requireMinValuesToBeZero().build()
+                            .verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testDoorMoveIfSupported() {
+        adoptSystemLevelPermission(/* Car.PERMISSION_CONTROL_CAR_DOORS = */
+                "android.car.permission.CONTROL_CAR_DOORS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DOOR_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_DOOR,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                            .requireZeroToBeContainedInMinMaxRanges().build().verify(
+                            mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testDoorLockIfSupported() {
+        adoptSystemLevelPermission(/* Car.PERMISSION_CONTROL_CAR_DOORS = */
+                "android.car.permission.CONTROL_CAR_DOORS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DOOR_LOCK,
+                        CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                        VehicleAreaType.VEHICLE_AREA_TYPE_DOOR,
+                        CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                        Boolean.class).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testMirrorZPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Z_POS,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testMirrorZMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Z_MOVE,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testMirrorYPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Y_POS,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testMirrorYMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Y_MOVE,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testMirrorLockIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_LOCK,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE, Boolean.class)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testMirrorFoldIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_FOLD,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE, Boolean.class)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testWindowPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.WINDOW_POS,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testWindowMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.WINDOW_MOVE,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testWindowLockIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.WINDOW_LOCK,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
     }
 
     @Test
     public void testDistanceDisplayUnitsIfSupported() {
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DISTANCE_DISPLAY_UNITS,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setPossibleConfigArrayValues(
-                DISTANCE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
-                mCarPropertyManager);
+        adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+                "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DISTANCE_DISPLAY_UNITS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleConfigArrayValues(
+                            DISTANCE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+                            .verifySetterWithConfigArrayValues().build().verify(
+                            mCarPropertyManager);
+            });
     }
 
     @Test
     public void testFuelVolumeDisplayUnitsIfSupported() {
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setPossibleConfigArrayValues(
-                VOLUME_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
-                mCarPropertyManager);
+        adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+                "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleConfigArrayValues(
+                            VOLUME_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+                            .verifySetterWithConfigArrayValues().build().verify(
+                            mCarPropertyManager);
+
+                });
+    }
+
+    @Test
+    public void testTirePressureIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_TIRES=*/
+                "android.car.permission.CAR_TIRES", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TIRE_PRESSURE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                            Float.class).requireMinMaxValues().setCarPropertyValueVerifier(
+                                    (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+                                            "TIRE_PRESSURE Float value"
+                                                    + " at Area ID equals to "
+                                                    + carPropertyValue.getAreaId()
+                                                    + " must be greater than or equal 0").that(
+                                    (Float) carPropertyValue.getValue()).isAtLeast(
+                                    0)).build().verify(
+                            mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testCriticallyLowTirePressureIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_TIRES=*/
+                "android.car.permission.CAR_TIRES", () -> {
+                    VehiclePropertyVerifier.newBuilder(
+                            VehiclePropertyIds.CRITICALLY_LOW_TIRE_PRESSURE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+                            Float.class).setCarPropertyValueVerifier(
+                                    (carPropertyConfig, carPropertyValue) -> {
+                                        int areaId = carPropertyValue.getAreaId();
+
+                                        assertWithMessage(
+                                                "CRITICALLY_LOW_TIRE_PRESSURE Float value"
+                                                        + "at Area ID equals to" + areaId
+                                                        + " must be greater than or equal 0")
+                                            .that((Float) carPropertyValue.getValue()).isAtLeast(0);
+
+                                        CarPropertyConfig<?> tirePressureConfig =
+                                                mCarPropertyManager.getCarPropertyConfig(
+                                                        VehiclePropertyIds.TIRE_PRESSURE);
+
+                                        if (tirePressureConfig == null
+                                                || tirePressureConfig.getMinValue(areaId) == null) {
+                                            return;
+                                        }
+
+                                        assertWithMessage(
+                                                "CRITICALLY_LOW_TIRE_PRESSURE Float value"
+                                                        + "at Area ID equals to" + areaId
+                                                        + " must not exceed"
+                                                        + " minFloatValue in TIRE_PRESSURE")
+                                                .that((Float) carPropertyValue.getValue()).isAtMost(
+                                                        (Float) tirePressureConfig
+                                                                .getMinValue(areaId));
+                                    }).build().verify(mCarPropertyManager);
+                });
     }
 
     @Test
     public void testTirePressureDisplayUnitsIfSupported() {
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setPossibleConfigArrayValues(
-                PRESSURE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
-                mCarPropertyManager);
+        adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+                "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+                    VehiclePropertyVerifier.newBuilder(
+                            VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleConfigArrayValues(
+                            PRESSURE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+                            .verifySetterWithConfigArrayValues().build().verify(
+                            mCarPropertyManager);
+
+                });
     }
 
     @Test
     public void testEvBatteryDisplayUnitsIfSupported() {
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setPossibleConfigArrayValues(
-                BATTERY_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
-                mCarPropertyManager);
+        adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+                "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleConfigArrayValues(
+                            BATTERY_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+                            .verifySetterWithConfigArrayValues().build().verify(
+                            mCarPropertyManager);
+
+                });
     }
 
     @Test
     public void testVehicleSpeedDisplayUnitsIfSupported() {
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Integer.class).setPossibleConfigArrayValues(
-                SPEED_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
-                mCarPropertyManager);
+        adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+                "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+                    VehiclePropertyVerifier.newBuilder(
+                            VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleConfigArrayValues(
+                            SPEED_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+                            .verifySetterWithConfigArrayValues().build().verify(
+                            mCarPropertyManager);
+                });
     }
 
     @Test
     public void testFuelConsumptionUnitsDistanceOverTimeIfSupported() {
-        VehiclePropertyVerifier.newBuilder(
-                VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Boolean.class).build().verify(mCarPropertyManager);
+        adoptSystemLevelPermission(Car.PERMISSION_VENDOR_EXTENSION, () -> {
+            VehiclePropertyVerifier.newBuilder(
+                    VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
     }
 
     @Test
@@ -793,22 +1139,24 @@
 
     @Test
     public void testFuelDoorOpenIfSupported() {
-        VehiclePropertyVerifier.newBuilder(
-                VehiclePropertyIds.FUEL_DOOR_OPEN,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Boolean.class).build().verify(mCarPropertyManager);
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FUEL_DOOR_OPEN,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
     }
 
     @Test
     public void testEvChargePortOpenIfSupported() {
-        VehiclePropertyVerifier.newBuilder(
-                VehiclePropertyIds.EV_CHARGE_PORT_OPEN,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Boolean.class).build().verify(mCarPropertyManager);
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_PORT_OPEN,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
     }
 
     @Test
@@ -900,11 +1248,13 @@
 
     @Test
     public void testEvChargeSwitchIfSupported() {
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_SWITCH,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
-                Boolean.class).build().verify(mCarPropertyManager);
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_ENERGY, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_SWITCH,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
     }
 
     @Test
@@ -942,28 +1292,1435 @@
 
     @Test
     public void testPerfSteeringAngleIfSupported() {
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        uiAutomation.adoptShellPermissionIdentity(Car.PERMISSION_READ_STEERING_STATE);
-
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_STEERING_ANGLE,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
-                Float.class).build().verify(mCarPropertyManager);
+        adoptSystemLevelPermission(Car.PERMISSION_READ_STEERING_STATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_STEERING_ANGLE,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                    Float.class).build().verify(mCarPropertyManager);
+        });
     }
 
     @Test
     public void testPerfRearSteeringAngleIfSupported() {
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        uiAutomation.adoptShellPermissionIdentity(Car.PERMISSION_READ_STEERING_STATE);
-
-        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_REAR_STEERING_ANGLE,
-                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
-                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
-                Float.class).build().verify(mCarPropertyManager);
+        adoptSystemLevelPermission(Car.PERMISSION_READ_STEERING_STATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_REAR_STEERING_ANGLE,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                    Float.class).build().verify(mCarPropertyManager);
+        });
     }
 
+    @Test
+    public void testEngineCoolantTempIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+                "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_COOLANT_TEMP,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                            Float.class).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testEngineOilLevelIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+                "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_OIL_LEVEL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setCarPropertyValueVerifier(
+                                    (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+                                    "ENGINE_OIL_LEVEL Integer value must be greater than or equal"
+                                            + " 0").that(
+                                    (Integer) carPropertyValue.getValue()).isAtLeast(
+                                    0)).build().verify(
+                            mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testEngineOilTempIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+                "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_OIL_TEMP,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                            Float.class).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testEngineRpmIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+                "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_RPM,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                            Float.class).setCarPropertyValueVerifier(
+                                    (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+                                    "ENGINE_RPM Float value must be greater than or equal 0").that(
+                                    (Float) carPropertyValue.getValue()).isAtLeast(
+                                    0)).build().verify(
+                            mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testPerfOdometerIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_MILEAGE=*/"android.car.permission.CAR_MILEAGE",
+                () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_ODOMETER,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                            Float.class).setCarPropertyValueVerifier(
+                            (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+                                    "PERF_ODOMETER Float value must be greater than or equal 0")
+                                    .that((Float) carPropertyValue.getValue()).isAtLeast(0))
+                            .build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testTurnSignalStateIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+                "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TURN_SIGNAL_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(TURN_SIGNAL_STATES).build()
+                            .verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testHeadlightsStateIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+                "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HEADLIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                            .build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testHighBeamLightsStateIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+                "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HIGH_BEAM_LIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                            .build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testFogLightsStateIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+                "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FOG_LIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                            .setCarPropertyValueVerifier(
+                                (carPropertyConfig, carPropertyValue) -> {
+                                    assertWithMessage(
+                                            "FRONT_FOG_LIGHTS_STATE must not be implemented"
+                                                    + "when FOG_LIGHTS_STATE is implemented")
+                                            .that(mCarPropertyManager.getCarPropertyConfig(
+                                                    VehiclePropertyIds.FRONT_FOG_LIGHTS_STATE))
+                                            .isNull();
+
+                                    assertWithMessage(
+                                            "REAR_FOG_LIGHTS_STATE must not be implemented"
+                                                    + "when FOG_LIGHTS_STATE is implemented")
+                                            .that(mCarPropertyManager.getCarPropertyConfig(
+                                                    VehiclePropertyIds.REAR_FOG_LIGHTS_STATE))
+                                            .isNull();
+                                }).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testHazardLightsStateIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+                "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HAZARD_LIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                            .build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testFrontFogLightsStateIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+                "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FRONT_FOG_LIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                            .setCarPropertyValueVerifier(
+                                (carPropertyConfig, carPropertyValue) -> {
+                                    assertWithMessage(
+                                            "FOG_LIGHTS_STATE must not be implemented"
+                                                    + "when FRONT_FOG_LIGHTS_STATE is implemented")
+                                            .that(mCarPropertyManager.getCarPropertyConfig(
+                                                    VehiclePropertyIds.FOG_LIGHTS_STATE))
+                                            .isNull();
+                                }).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    public void testRearFogLightsStateIfSupported() {
+        adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+                "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.REAR_FOG_LIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                            .setCarPropertyValueVerifier(
+                                (carPropertyConfig, carPropertyValue) -> {
+                                    assertWithMessage(
+                                            "FOG_LIGHTS_STATE must not be implemented"
+                                                    + "when REAR_FOG_LIGHTS_STATE is implemented")
+                                            .that(mCarPropertyManager.getCarPropertyConfig(
+                                                    VehiclePropertyIds.FOG_LIGHTS_STATE))
+                                            .isNull();
+                                }).build().verify(mCarPropertyManager);
+                });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testCabinLightsStateIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_READ_INTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.CABIN_LIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testReadingLightsStateIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_READ_INTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.READING_LIGHTS_STATE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testVehicleCurbWeightIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_PRIVILEGED_CAR_INFO, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.VEHICLE_CURB_WEIGHT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+                    Integer.class).setConfigArrayVerifier(configArray -> {
+                assertWithMessage(
+                        "VEHICLE_CURB_WEIGHT configArray must contain the gross weight in "
+                                + "kilograms").that(configArray).hasSize(1);
+                assertWithMessage(
+                        "VEHICLE_CURB_WEIGHT configArray[0] must contain the gross weight in "
+                                + "kilograms and be greater than zero").that(
+                        configArray.get(0)).isGreaterThan(0);
+            }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+                Integer curbWeightKg = (Integer) carPropertyValue.getValue();
+                Integer grossWeightKg = carPropertyConfig.getConfigArray().get(0);
+
+                assertWithMessage("VEHICLE_CURB_WEIGHT must be greater than zero").that(
+                        curbWeightKg).isGreaterThan(0);
+                assertWithMessage("VEHICLE_CURB_WEIGHT must be less than the gross weight").that(
+                        curbWeightKg).isLessThan(grossWeightKg);
+            }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHeadlightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HEADLIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testTrailerPresentIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_PRIVILEGED_CAR_INFO, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TRAILER_PRESENT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).setPossibleCarPropertyValues(
+                    ImmutableSet.of(/*TrailerState.UNKNOWN=*/
+                            0, /*TrailerState.NOT_PRESENT*/
+                            1, /*TrailerState.PRESENT=*/2, /*TrailerState.ERROR=*/
+                            3)).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHighBeamLightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HIGH_BEAM_LIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testFogLightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FOG_LIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+                        assertWithMessage("FRONT_FOG_LIGHTS_SWITCH must not be implemented"
+                                + "when FOG_LIGHTS_SWITCH is implemented")
+                                .that(mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.FRONT_FOG_LIGHTS_SWITCH)).isNull();
+
+                        assertWithMessage("REAR_FOG_LIGHTS_SWITCH must not be implemented"
+                                + "when FOG_LIGHTS_SWITCH is implemented")
+                                .that(mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.REAR_FOG_LIGHTS_SWITCH)).isNull();
+                    }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHazardLightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HAZARD_LIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testFrontFogLightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FRONT_FOG_LIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+                        assertWithMessage("FOG_LIGHTS_SWITCH must not be implemented"
+                                + "when FRONT_FOG_LIGHTS_SWITCH is implemented")
+                                .that(mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.FOG_LIGHTS_SWITCH)).isNull();
+                    }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testRearFogLightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.REAR_FOG_LIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+                        assertWithMessage("FOG_LIGHTS_SWITCH must not be implemented"
+                                + "when REAR_FOG_LIGHTS_SWITCH is implemented")
+                                .that(mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.FOG_LIGHTS_SWITCH)).isNull();
+                    }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testCabinLightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_INTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.CABIN_LIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testReadingLightsSwitchIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_INTERIOR_LIGHTS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.READING_LIGHTS_SWITCH,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig"})
+    public void testSeatMemorySelectIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_MEMORY_SELECT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().requireMinValuesToBeZero()
+                    .setCarPropertyConfigVerifier(carPropertyConfig -> {
+                        int[] areaIds = carPropertyConfig.getAreaIds();
+                        CarPropertyConfig<?> seatMemorySetCarPropertyConfig =
+                                mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.SEAT_MEMORY_SET);
+
+                        assertWithMessage("SEAT_MEMORY_SET must be implemented if "
+                                + "SEAT_MEMORY_SELECT is implemented").that(
+                                        seatMemorySetCarPropertyConfig).isNotNull();
+
+                        assertWithMessage("SEAT_MEMORY_SELECT area IDs must match the area IDs of "
+                                + "SEAT_MEMORY_SET").that(Arrays.stream(areaIds).boxed().collect(
+                                        Collectors.toList()))
+                                .containsExactlyElementsIn(Arrays.stream(
+                                        seatMemorySetCarPropertyConfig.getAreaIds()).boxed()
+                                        .collect(Collectors.toList()));
+
+                        for (int areaId : areaIds) {
+                            Integer seatMemorySetAreaIdMaxValue =
+                                    (Integer) seatMemorySetCarPropertyConfig.getMaxValue(areaId);
+                            assertWithMessage("SEAT_MEMORY_SET - area ID: " + areaId
+                                    + " must have max value defined")
+                                    .that(seatMemorySetAreaIdMaxValue).isNotNull();
+                            assertWithMessage("SEAT_MEMORY_SELECT - area ID: " + areaId
+                                    + "'s max value must be equal to SEAT_MEMORY_SET's max value"
+                                    + " under the same area ID")
+                                    .that(seatMemorySetAreaIdMaxValue)
+                                    .isEqualTo(carPropertyConfig.getMaxValue(areaId));
+                        }
+                    }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig"})
+    public void testSeatMemorySetIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_MEMORY_SET,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().requireMinValuesToBeZero()
+                    .setCarPropertyConfigVerifier(carPropertyConfig -> {
+                        int[] areaIds = carPropertyConfig.getAreaIds();
+                        CarPropertyConfig<?> seatMemorySelectCarPropertyConfig =
+                                mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.SEAT_MEMORY_SELECT);
+
+                        assertWithMessage("SEAT_MEMORY_SELECT must be implemented if "
+                                + "SEAT_MEMORY_SET is implemented").that(
+                                seatMemorySelectCarPropertyConfig).isNotNull();
+
+                        assertWithMessage("SEAT_MEMORY_SET area IDs must match the area IDs of "
+                                + "SEAT_MEMORY_SELECT").that(Arrays.stream(areaIds).boxed().collect(
+                                        Collectors.toList()))
+                                .containsExactlyElementsIn(Arrays.stream(
+                                        seatMemorySelectCarPropertyConfig.getAreaIds()).boxed()
+                                        .collect(Collectors.toList()));
+
+                        for (int areaId : areaIds) {
+                            Integer seatMemorySelectAreaIdMaxValue =
+                                    (Integer) seatMemorySelectCarPropertyConfig.getMaxValue(areaId);
+                            assertWithMessage("SEAT_MEMORY_SELECT - area ID: " + areaId
+                                    + " must have max value defined")
+                                    .that(seatMemorySelectAreaIdMaxValue).isNotNull();
+                            assertWithMessage("SEAT_MEMORY_SET - area ID: " + areaId
+                                    + "'s max value must be equal to SEAT_MEMORY_SELECT's max value"
+                                    + " under the same area ID")
+                                    .that(seatMemorySelectAreaIdMaxValue)
+                                    .isEqualTo(carPropertyConfig.getMaxValue(areaId));
+                        }
+                    }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatBeltBuckledIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BELT_BUCKLED,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatBeltHeightPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BELT_HEIGHT_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatBeltHeightMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BELT_HEIGHT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatForeAftPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_FORE_AFT_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatForeAftMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_FORE_AFT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatBackrestAngle1PosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatBackrestAngle1MoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatBackrestAngle2PosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testSeatBackrestAngle2MoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatHeightPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEIGHT_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatHeightMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEIGHT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatDepthPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_DEPTH_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatDepthMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_DEPTH_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatTiltPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_TILT_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatTiltMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_TILT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatLumbarForeAftPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatLumbarForeAftMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatLumbarSideSupportPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatLumbarSideSupportMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatHeadrestHeightMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_HEIGHT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatHeadrestAnglePosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_ANGLE_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatHeadrestAngleMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_ANGLE_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatHeadrestForeAftPosIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_POS,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatHeadrestForeAftMoveIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_MOVE,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class).requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testSeatOccupancyIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_OCCUPANCY,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Integer.class)
+                    .setPossibleCarPropertyValues(VEHICLE_SEAT_OCCUPANCY_STATES).build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacDefrosterIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_DEFROSTER,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHvacElectricDefrosterOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_ELECTRIC_DEFROSTER_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHvacSideMirrorHeatIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_SIDE_MIRROR_HEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues().requireMinValuesToBeZero().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHvacSteeringWheelHeatIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues().requireZeroToBeContainedInMinMaxRanges()
+                    .build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHvacTemperatureDisplayUnitsIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).setPossibleConfigArrayValues(
+                    HVAC_TEMPERATURE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+                    .verifySetterWithConfigArrayValues().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    public void testHvacTemperatureValueSuggestionIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Float[].class).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacPowerOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_POWER_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setConfigArrayVerifier(configArray -> {
+                CarPropertyConfig<?> hvacPowerOnCarPropertyConfig =
+                        mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON);
+                for (int powerDependentProperty : configArray) {
+                    CarPropertyConfig<?> powerDependentCarPropertyConfig =
+                            mCarPropertyManager.getCarPropertyConfig(powerDependentProperty);
+                    if (powerDependentCarPropertyConfig == null) {
+                        continue;
+                    }
+                    assertWithMessage(
+                            "HVAC_POWER_ON configArray must only contain VehicleAreaSeat type "
+                                    + "properties: " + VehiclePropertyIds.toString(
+                                    powerDependentProperty)).that(
+                            powerDependentCarPropertyConfig.getAreaType()).isEqualTo(
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT);
+                    for (int powerDependentAreaId : powerDependentCarPropertyConfig.getAreaIds()) {
+                        boolean powerDependentAreaIdIsContained = false;
+                        for (int hvacPowerOnAreaId : hvacPowerOnCarPropertyConfig.getAreaIds()) {
+                            if ((powerDependentAreaId & hvacPowerOnAreaId)
+                                    == powerDependentAreaId) {
+                                powerDependentAreaIdIsContained = true;
+                                break;
+                            }
+                        }
+                        assertWithMessage(
+                                "HVAC_POWER_ON's area IDs must contain the area IDs"
+                                        + " of power dependent property: "
+                                        + VehiclePropertyIds.toString(
+                                        powerDependentProperty)).that(
+                                        powerDependentAreaIdIsContained).isTrue();
+                    }
+                }
+            }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacFanSpeedIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_FAN_SPEED,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).requireMinMaxValues().setPossiblyDependentOnHvacPowerOn().build()
+                    .verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacFanDirectionAvailableIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+                    Integer[].class).setPossiblyDependentOnHvacPowerOn().setAreaIdsVerifier(
+                    areaIds -> {
+                        CarPropertyConfig<?> hvacFanDirectionCarPropertyConfig =
+                                mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.HVAC_FAN_DIRECTION);
+                        assertWithMessage("HVAC_FAN_DIRECTION must be implemented if "
+                                + "HVAC_FAN_DIRECTION_AVAILABLE is implemented").that(
+                                hvacFanDirectionCarPropertyConfig).isNotNull();
+
+                        assertWithMessage(
+                                "HVAC_FAN_DIRECTION_AVAILABLE area IDs must match the area IDs of "
+                                        + "HVAC_FAN_DIRECTION").that(Arrays.stream(
+                                areaIds).boxed().collect(
+                                Collectors.toList())).containsExactlyElementsIn(Arrays.stream(
+                                hvacFanDirectionCarPropertyConfig.getAreaIds()).boxed().collect(
+                                Collectors.toList()));
+
+                    }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+                Integer[] fanDirectionValues = (Integer[]) carPropertyValue.getValue();
+                assertWithMessage(
+                        "HVAC_FAN_DIRECTION_AVAILABLE area ID: " + carPropertyValue.getAreaId()
+                                + " must have at least 1 direction defined").that(
+                        fanDirectionValues.length).isAtLeast(1);
+                assertWithMessage(
+                        "HVAC_FAN_DIRECTION_AVAILABLE area ID: " + carPropertyValue.getAreaId()
+                                + " values all must all be unique: " + Arrays.toString(
+                                fanDirectionValues)).that(fanDirectionValues.length).isEqualTo(
+                        ImmutableSet.copyOf(fanDirectionValues).size());
+                for (Integer fanDirection : fanDirectionValues) {
+                    assertWithMessage("HVAC_FAN_DIRECTION_AVAILABLE's area ID: "
+                            + carPropertyValue.getAreaId()
+                            + " must be a valid combination of fan directions").that(
+                            fanDirection).isIn(ALL_POSSIBLE_HVAC_FAN_DIRECTIONS);
+                }
+            }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacFanDirectionIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_FAN_DIRECTION,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).setPossiblyDependentOnHvacPowerOn().setAreaIdsVerifier(
+                    areaIds -> {
+                        CarPropertyConfig<?> hvacFanDirectionAvailableCarPropertyConfig =
+                                mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE);
+                        assertWithMessage("HVAC_FAN_DIRECTION_AVAILABLE must be implemented if "
+                                + "HVAC_FAN_DIRECTION is implemented").that(
+                                hvacFanDirectionAvailableCarPropertyConfig).isNotNull();
+
+                        assertWithMessage("HVAC_FAN_DIRECTION area IDs must match the area IDs of "
+                                + "HVAC_FAN_DIRECTION_AVAILABLE").that(Arrays.stream(
+                                areaIds).boxed().collect(
+                                Collectors.toList())).containsExactlyElementsIn(Arrays.stream(
+                                hvacFanDirectionAvailableCarPropertyConfig.getAreaIds()).boxed()
+                                .collect(Collectors.toList()));
+
+                    }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+                CarPropertyValue<Integer[]> hvacFanDirectionAvailableCarPropertyValue =
+                        mCarPropertyManager.getProperty(
+                                VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE,
+                                carPropertyValue.getAreaId());
+                assertWithMessage("HVAC_FAN_DIRECTION_AVAILABLE value must be available").that(
+                        hvacFanDirectionAvailableCarPropertyValue).isNotNull();
+
+                assertWithMessage("HVAC_FAN_DIRECTION area ID " + carPropertyValue.getAreaId()
+                        + " value must be in list for HVAC_FAN_DIRECTION_AVAILABLE").that(
+                        carPropertyValue.getValue()).isIn(
+                        Arrays.asList(hvacFanDirectionAvailableCarPropertyValue.getValue()));
+            }).build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacTemperatureCurrentIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Float.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacTemperatureSetIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.Builder<Float> hvacTempSetVerifierBuilder =
+                    VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_SET,
+                            CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                            VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                            CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                            Float.class).setPossiblyDependentOnHvacPowerOn().setConfigArrayVerifier(
+                            configArray -> {
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET config array must be size 6").that(
+                                        configArray.size()).isEqualTo(6);
+
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET lower bound must be less than the "
+                                                + "upper bound for "
+                                                + "the supported temperatures in Celsius").that(
+                                        configArray.get(0)).isLessThan(configArray.get(1));
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET increment in Celsius must be "
+                                                + "greater than 0").that(
+                                        configArray.get(2)).isGreaterThan(0);
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET increment in Celsius must be less "
+                                                + "than the "
+                                                + "difference between the upper and lower bound "
+                                                + "supported "
+                                                + "temperatures").that(
+                                        configArray.get(2)).isLessThan(
+                                        configArray.get(1) - configArray.get(0));
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET increment in Celsius must evenly "
+                                        + "space the gap "
+                                                + "between upper and lower bound").that(
+                                        (configArray.get(1) - configArray.get(0)) % configArray.get(
+                                                2)).isEqualTo(0);
+
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET lower bound must be less than the "
+                                        + "upper bound for "
+                                                + "the supported temperatures in Fahrenheit").that(
+                                        configArray.get(3)).isLessThan(configArray.get(4));
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET increment in Fahrenheit must be "
+                                        + "greater than 0").that(
+                                        configArray.get(5)).isGreaterThan(0);
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET increment in Fahrenheit must be "
+                                        + "less than the "
+                                                + "difference between the upper and lower bound "
+                                                + "supported "
+                                                + "temperatures").that(
+                                        configArray.get(5)).isLessThan(
+                                        configArray.get(4) - configArray.get(3));
+                                assertWithMessage(
+                                        "HVAC_TEMPERATURE_SET increment in Fahrenheit must evenly"
+                                                + " space the gap "
+                                                + "between upper and lower bound").that(
+                                        (configArray.get(4) - configArray.get(3)) % configArray.get(
+                                                5)).isEqualTo(0);
+
+                            });
+
+            CarPropertyConfig<?> hvacTempSetConfig = mCarPropertyManager.getCarPropertyConfig(
+                    VehiclePropertyIds.HVAC_TEMPERATURE_SET);
+            if (hvacTempSetConfig != null) {
+                ImmutableSet.Builder<Float> possibleHvacTempSetValuesBuilder =
+                        ImmutableSet.builder();
+                for (int possibleHvacTempSetValue = hvacTempSetConfig.getConfigArray().get(0);
+                        possibleHvacTempSetValue <= hvacTempSetConfig.getConfigArray().get(1);
+                        possibleHvacTempSetValue += hvacTempSetConfig.getConfigArray().get(2)) {
+                    possibleHvacTempSetValuesBuilder.add((float) possibleHvacTempSetValue / 10.0f);
+                }
+                hvacTempSetVerifierBuilder.setPossibleCarPropertyValues(
+                        possibleHvacTempSetValuesBuilder.build());
+            }
+
+            hvacTempSetVerifierBuilder.build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacAcOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_AC_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacMaxAcOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_MAX_AC_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacMaxDefrostOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_MAX_DEFROST_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacRecircOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_RECIRC_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacAutoOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_AUTO_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacSeatTemperatureIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_SEAT_TEMPERATURE,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).setPossiblyDependentOnHvacPowerOn().requireMinMaxValues()
+                    .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacActualFanSpeedRpmIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_ACTUAL_FAN_SPEED_RPM,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacAutoRecircOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_AUTO_RECIRC_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+                    mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacSeatVentilationIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_SEAT_VENTILATION,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Integer.class).setPossiblyDependentOnHvacPowerOn().requireMinMaxValues()
+                    .requireMinValuesToBeZero().build().verify(mCarPropertyManager);
+        });
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+            "android.car.hardware.property.CarPropertyManager#getProperty",
+            "android.car.hardware.property.CarPropertyManager#setProperty",
+            "android.car.hardware.property.CarPropertyManager#registerCallback",
+            "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+    public void testHvacDualOnIfSupported() {
+        adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+            VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_DUAL_ON,
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                    VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+                    CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                    Boolean.class).setPossiblyDependentOnHvacPowerOn().setAreaIdsVerifier(
+                    areaIds -> {
+                        CarPropertyConfig<?> hvacTempSetCarPropertyConfig =
+                                mCarPropertyManager.getCarPropertyConfig(
+                                        VehiclePropertyIds.HVAC_TEMPERATURE_SET);
+                        if (hvacTempSetCarPropertyConfig == null) {
+                            return;
+                        }
+                        ImmutableSet<Integer> hvacTempSetAreaIds = ImmutableSet.copyOf(
+                                Arrays.stream(
+                                        hvacTempSetCarPropertyConfig.getAreaIds()).boxed().collect(
+                                        Collectors.toList()));
+                        ImmutableSet.Builder<Integer> allPossibleHvacDualOnAreaIdsBuilder =
+                                ImmutableSet.builder();
+                        for (int i = 2; i <= hvacTempSetAreaIds.size(); i++) {
+                            allPossibleHvacDualOnAreaIdsBuilder.addAll(Sets.combinations(
+                                    hvacTempSetAreaIds, i).stream().map(areaIdCombo -> {
+                                Integer possibleHvacDualOnAreaId = 0;
+                                for (Integer areaId : areaIdCombo) {
+                                    possibleHvacDualOnAreaId |= areaId;
+                                }
+                                return possibleHvacDualOnAreaId;
+                            }).collect(Collectors.toList()));
+                        }
+                        ImmutableSet<Integer> allPossibleHvacDualOnAreaIds =
+                                allPossibleHvacDualOnAreaIdsBuilder.build();
+                        for (int areaId : areaIds) {
+                            assertWithMessage("HVAC_DUAL_ON area ID: " + areaId
+                                    + " must be a combination of HVAC_TEMPERATURE_SET area IDs: "
+                                    + Arrays.toString(
+                                    hvacTempSetCarPropertyConfig.getAreaIds())).that(areaId).isIn(
+                                    allPossibleHvacDualOnAreaIds);
+
+                        }
+                    }).build().verify(mCarPropertyManager);
+        });
+    }
 
     @SuppressWarnings("unchecked")
     @Test
@@ -1080,7 +2837,17 @@
 
         // Test for continuous properties
         int vehicleSpeed = VehiclePropertyIds.PERF_VEHICLE_SPEED;
-        CarPropertyEventCounter speedListenerUI = new CarPropertyEventCounter();
+        CarPropertyConfig<?> carPropertyConfig = mCarPropertyManager.getCarPropertyConfig(
+                VehiclePropertyIds.PERF_VEHICLE_SPEED);
+        float secondsToMillis = 1_000;
+        long bufferMillis = 1_000; // 1 second
+        // timeoutMillis is set to the maximum expected time needed to receive the required
+        // number of PERF_VEHICLE_SPEED events for test. If the test does not receive the
+        // required number of events before the timeout expires, it fails.
+        long timeoutMillis =
+                ((long) ((1.0f / carPropertyConfig.getMinSampleRate()) * secondsToMillis
+                        * UI_RATE_EVENT_COUNTER)) + bufferMillis;
+        CarPropertyEventCounter speedListenerUI = new CarPropertyEventCounter(timeoutMillis);
         CarPropertyEventCounter speedListenerFast = new CarPropertyEventCounter();
 
         assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isEqualTo(NO_EVENTS);
@@ -1090,22 +2857,22 @@
         assertThat(speedListenerFast.receivedError(vehicleSpeed)).isEqualTo(NO_EVENTS);
         assertThat(speedListenerFast.receivedErrorWithErrorCode(vehicleSpeed)).isEqualTo(NO_EVENTS);
 
+        speedListenerUI.resetCountDownLatch(UI_RATE_EVENT_COUNTER);
         mCarPropertyManager.registerCallback(speedListenerUI, vehicleSpeed,
                 CarPropertyManager.SENSOR_RATE_UI);
         mCarPropertyManager.registerCallback(speedListenerFast, vehicleSpeed,
                 CarPropertyManager.SENSOR_RATE_FASTEST);
-        speedListenerUI.resetCountDownLatch(UI_RATE_EVENT_COUNTER);
         speedListenerUI.assertOnChangeEventCalled();
+        mCarPropertyManager.unregisterCallback(speedListenerUI);
+        mCarPropertyManager.unregisterCallback(speedListenerFast);
+
         assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isGreaterThan(NO_EVENTS);
-        assertThat(speedListenerFast.receivedEvent(vehicleSpeed)).isGreaterThan(
+        assertThat(speedListenerFast.receivedEvent(vehicleSpeed)).isAtLeast(
                 speedListenerUI.receivedEvent(vehicleSpeed));
         // The test did not change property values, it should not get error with error codes.
         assertThat(speedListenerUI.receivedErrorWithErrorCode(vehicleSpeed)).isEqualTo(NO_EVENTS);
         assertThat(speedListenerFast.receivedErrorWithErrorCode(vehicleSpeed)).isEqualTo(NO_EVENTS);
 
-        mCarPropertyManager.unregisterCallback(speedListenerFast);
-        mCarPropertyManager.unregisterCallback(speedListenerUI);
-
         // Test for on_change properties
         int nightMode = VehiclePropertyIds.NIGHT_MODE;
         CarPropertyEventCounter nightModeListener = new CarPropertyEventCounter();
@@ -1114,7 +2881,6 @@
         nightModeListener.assertOnChangeEventCalled();
         assertThat(nightModeListener.receivedEvent(nightMode)).isEqualTo(1);
         mCarPropertyManager.unregisterCallback(nightModeListener);
-
     }
 
     @Test
@@ -1157,7 +2923,7 @@
     @Test
     public void testUnregisterWithPropertyId() throws Exception {
         // Ignores the test if wheel_tick property does not exist in the car.
-        Assume.assumeTrue("WheelTick is not available, skip unregisterCallback test",
+        assumeTrue("WheelTick is not available, skip unregisterCallback test",
                 mCarPropertyManager.isPropertyAvailable(
                         VehiclePropertyIds.WHEEL_TICK, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
 
@@ -1170,7 +2936,7 @@
         int eventCounter = getCounterBySampleRate(maxSampleRateHz);
 
         // Ignores the test if sampleRates for properties are too low.
-        Assume.assumeTrue("The SampleRates for properties are too low, "
+        assumeTrue("The SampleRates for properties are too low, "
                 + "skip testUnregisterWithPropertyId test", eventCounter != 0);
         CarPropertyEventCounter speedAndWheelTicksListener = new CarPropertyEventCounter();
 
@@ -1239,6 +3005,15 @@
         private final SparseArray<Integer> mErrorWithErrorCodeCounter = new SparseArray<>();
         private int mCounter = FAST_OR_FASTEST_EVENT_COUNTER;
         private CountDownLatch mCountDownLatch = new CountDownLatch(mCounter);
+        private final long mTimeoutMillis;
+
+        CarPropertyEventCounter(long timeoutMillis) {
+            mTimeoutMillis = timeoutMillis;
+        }
+
+        CarPropertyEventCounter() {
+            this(WAIT_CALLBACK);
+        }
 
         public int receivedEvent(int propId) {
             int val;
@@ -1295,18 +3070,19 @@
         }
 
         public void assertOnChangeEventCalled() throws InterruptedException {
-            if (!mCountDownLatch.await(WAIT_CALLBACK, TimeUnit.MILLISECONDS)) {
-                throw new IllegalStateException("Callback is not called:" + mCounter + "times in "
-                        + WAIT_CALLBACK + " ms.");
+            if (!mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS)) {
+                throw new IllegalStateException(
+                        "Callback is not called " + mCounter + "times in " + mTimeoutMillis
+                                + " ms. It was only called " + (mCounter
+                                - mCountDownLatch.getCount()) + " times.");
             }
         }
 
         public void assertOnChangeEventNotCalled() throws InterruptedException {
             // Once get an event, fail the test.
             mCountDownLatch = new CountDownLatch(1);
-            if (mCountDownLatch.await(WAIT_CALLBACK, TimeUnit.MILLISECONDS)) {
-                throw new IllegalStateException("Callback is called in "
-                        + WAIT_CALLBACK + " ms.");
+            if (mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS)) {
+                throw new IllegalStateException("Callback is called in " + mTimeoutMillis + " ms.");
             }
         }
 
diff --git a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
index 80229ec..3948687 100644
--- a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
+++ b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
@@ -16,8 +16,6 @@
 
 package android.car.cts;
 
-import static com.android.compatibility.common.util.SystemUtil.eventually;
-
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -27,6 +25,12 @@
 
 import android.app.UiAutomation;
 import android.car.Car;
+import android.car.annotation.ApiRequirements;
+import android.car.test.ApiCheckerRule;
+import android.car.test.ApiCheckerRule.IgnoreInvalidApi;
+import android.car.test.ApiCheckerRule.SupportedVersionTest;
+import android.car.test.ApiCheckerRule.UnsupportedVersionTest;
+import android.car.test.ApiCheckerRule.UnsupportedVersionTest.Behavior;
 import android.car.user.CarUserManager;
 import android.car.user.CarUserManager.UserLifecycleEvent;
 import android.car.user.CarUserManager.UserLifecycleListener;
@@ -34,16 +38,18 @@
 import android.os.NewUserResponse;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 
+import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.io.BufferedReader;
@@ -60,6 +66,11 @@
     private static final int TIMEOUT_MS = 60_000;
     private static final int WAIT_TIME_MS = 1_000;
 
+    // TODO(b/242350638): move to super class (although it would need to call
+    // disableAnnotationsCheck()
+    @Rule
+    public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -67,46 +78,131 @@
     }
 
     @Test
-    @Ignore("b/234674080")
+    @ApiTest(apis = {"com.android.internal.car.CarServiceHelperService#dump(PrintWriter,String[])"})
+    @IgnoreInvalidApi(reason = "Class not in classpath as it's indirectly tested using dumpsys")
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
     public void testCarServiceHelperServiceDump() throws Exception {
-        assumeThat("System_server_dumper not implemented.",
-                executeShellCommand("service check system_server_dumper"),
-                containsStringIgnoringCase("system_server_dumper: found"));
+        assumeSystemServerDumpSupported();
 
         assertWithMessage("System server dumper")
                 .that(executeShellCommand("dumpsys system_server_dumper --list"))
                 .contains("CarServiceHelper");
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "com.android.internal.car.CarServiceHelperServiceUpdatable#dump(PrintWriter,String[])"
+    })
+    @IgnoreInvalidApi(reason = "Class not in classpath as it's indirectly tested using dumpsys")
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
+    public void testCarServiceHelperServiceDump_carServiceProxy() throws Exception {
+        assumeSystemServerDumpSupported();
 
         assertWithMessage("CarServiceHelperService dump")
                 .that(executeShellCommand("dumpsys system_server_dumper --name CarServiceHelper"))
                 .contains("CarServiceProxy");
+    }
 
-        // Test setSafeMode
-        try {
-            executeShellCommand("cmd car_service emulate-driving-state drive");
+    @Test
+    @ApiTest(apis = {
+            "com.android.internal.car.CarServiceHelperServiceUpdatable#dump(PrintWriter,String[])"
+    })
+    @IgnoreInvalidApi(reason = "Class not in classpath as it's indirectly tested using dumpsys")
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
+    public void testCarServiceHelperServiceDump_serviceStacks() throws Exception {
+        assumeSystemServerDumpSupported();
 
-            eventually(()-> assertWithMessage("CarServiceHelperService dump")
-                    .that(executeShellCommand(
-                            "dumpsys system_server_dumper --name CarServiceHelper"))
-                    .contains("Safe to run device policy operations: false"));
-        } finally {
-            executeShellCommand("cmd car_service emulate-driving-state park");
-        }
-
-        eventually(()-> assertWithMessage("CarServiceHelperService dump")
-                .that(executeShellCommand("dumpsys system_server_dumper --name CarServiceHelper"))
-                .contains("Safe to run device policy operations: true"));
-
-        // Test dumpServiceStacks
         assertWithMessage("CarServiceHelperService dump")
-                .that(executeShellCommand("dumpsys system_server_dumper --name CarServiceHelper"
-                        + " --dump-service-stacks"))
+                .that(dumpCarServiceHelper("--dump-service-stacks"))
                 .contains("dumpServiceStacks ANR file path=/data/anr/anr_");
     }
 
+    @Test
+    @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_CREATED"})
+    @SupportedVersionTest(unsupportedVersionTest =
+            "testSendUserLifecycleEventAndOnUserCreated_unsupportedVersion")
+    public void testSendUserLifecycleEventAndOnUserCreated_supportedVersion() throws Exception {
+        testSendUserLifecycleEventAndOnUserCreated(/*onSupportedVersion=*/ true);
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_CREATED"})
+    @UnsupportedVersionTest(behavior = Behavior.EXPECT_PASS,
+            supportedVersionTest = "testSendUserLifecycleEventAndOnUserCreated_supportedVersion")
+    public void testSendUserLifecycleEventAndOnUserCreated_unsupportedVersion() throws Exception {
+        testSendUserLifecycleEventAndOnUserCreated(/*onSupportedVersion=*/ false);
+    }
+
+    private void testSendUserLifecycleEventAndOnUserCreated(boolean onSupportedVersion)
+            throws Exception {
+        // Add listener to check if user started
+        CarUserManager carUserManager = (CarUserManager) getCar()
+                .getCarManager(Car.CAR_USER_SERVICE);
+        LifecycleListener listener = new LifecycleListener();
+        carUserManager.addListener(Runnable::run, listener);
+
+        NewUserResponse response = null;
+        UserManager userManager = null;
+        try {
+            // get create User permissions
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(android.Manifest.permission.CREATE_USERS);
+
+            // CreateUser
+            userManager = mContext.getSystemService(UserManager.class);
+            response = userManager.createUser(new NewUserRequest.Builder().build());
+            assertThat(response.isSuccessful()).isTrue();
+
+            int userId = response.getUser().getIdentifier();
+
+            if (onSupportedVersion) {
+                listener.assertEventReceived(
+                        userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED);
+                // check the dump stack
+                assertUserLifecycleEventLogged(
+                        CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED, userId);
+            } else {
+                listener.assertEventNotReceived(
+                        userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED);
+            }
+        } finally {
+            // Clean up the user that was previously created.
+            userManager.removeUser(response.getUser());
+            carUserManager.removeListener(listener);
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+
     @FlakyTest(bugId = 222167696)
     @Test
-    public void testSendUserLifecycleEventAndOnUserRemoved() throws Exception {
+    @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_REMOVED"})
+    @SupportedVersionTest(unsupportedVersionTest =
+            "testSendUserLifecycleEventAndOnUserRemoved_unsupportedVersion")
+    public void testSendUserLifecycleEventAndOnUserRemoved_supportedVersion() throws Exception {
+        testSendUserLifecycleEventAndOnUserRemoved(/*onSupportedVersion=*/ true);
+    }
+
+    @FlakyTest(bugId = 222167696)
+    @Test
+    @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_REMOVED"})
+    @UnsupportedVersionTest(behavior = Behavior.EXPECT_PASS,
+            supportedVersionTest = "testSendUserLifecycleEventAndOnUserRemoved_supportedVersion")
+    public void testSendUserLifecycleEventAndOnUserRemoved_unsupportedVersion() throws Exception {
+        testSendUserLifecycleEventAndOnUserRemoved(/*onSupportedVersion=*/ false);
+    }
+
+    private static void assumeSystemServerDumpSupported() throws IOException {
+        assumeThat("System_server_dumper not implemented.",
+                executeShellCommand("service check system_server_dumper"),
+                containsStringIgnoringCase("system_server_dumper: found"));
+    }
+
+    private void testSendUserLifecycleEventAndOnUserRemoved(boolean onSupportedVersion)
+            throws Exception {
         // Add listener to check if user started
         CarUserManager carUserManager = (CarUserManager) getCar()
                 .getCarManager(Car.CAR_USER_SERVICE);
@@ -129,11 +225,23 @@
             int userId = response.getUser().getIdentifier();
             startUser(userId);
             listener.assertEventReceived(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING);
+            // check the dump stack
+            assertUserLifecycleEventLogged(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING,
+                    userId);
 
             // TestOnUserRemoved call
             userRemoved = userManager.removeUser(response.getUser());
-            // check the dump stack
-            assertLastUserRemoved(userId);
+
+            if (onSupportedVersion) {
+                listener.assertEventReceived(
+                        userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_REMOVED);
+                // check the dump stack
+                assertUserLifecycleEventLogged(
+                        CarUserManager.USER_LIFECYCLE_EVENT_TYPE_REMOVED, userId);
+            } else {
+                listener.assertEventNotReceived(
+                        userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_REMOVED);
+            }
         } finally {
             if (!userRemoved && response != null && response.isSuccessful()) {
                 userManager.removeUser(response.getUser());
@@ -144,12 +252,17 @@
         }
     }
 
-    private void assertLastUserRemoved(int userId) throws Exception {
+    private void assertUserLifecycleEventLogged(int eventType, int userId) throws Exception {
+        assertUserLifecycleEventLogged(eventType, UserHandle.USER_NULL, userId);
+    }
+
+    private void assertUserLifecycleEventLogged(int eventType, int fromUserId, int toUserId)
+            throws Exception {
         // check for the logcat
         // TODO(b/210874444): Use logcat helper from
         // cts/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java
-        String match = String.format("car_service_on_user_lifecycle: [%d,%d,%d]", 9,
-                -10000, userId);
+        String match = String.format("car_service_on_user_lifecycle: [%d,%d,%d]", eventType,
+                fromUserId, toUserId);
         long timeout = 60_000;
         long startTime = SystemClock.elapsedRealtime();
         UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -174,6 +287,15 @@
 
     }
 
+    private String dumpCarServiceHelper(String...args) throws IOException {
+        StringBuilder cmd = new StringBuilder(
+                "dumpsys system_server_dumper --name CarServiceHelper");
+        for (String arg : args) {
+            cmd.append(' ').append(arg);
+        }
+        return executeShellCommand(cmd.toString());
+    }
+
     // TODO(214100537): Improve listener by removing sleep.
     private final class LifecycleListener implements UserLifecycleListener {
 
@@ -202,6 +324,20 @@
             fail("Event" + eventType + " was not received within timeoutMs: " + TIMEOUT_MS);
         }
 
+
+        public void assertEventNotReceived(int userId, int eventType)
+                throws InterruptedException {
+            long startTime = SystemClock.elapsedRealtime();
+            while (SystemClock.elapsedRealtime() - startTime < TIMEOUT_MS) {
+                boolean result = checkEvent(userId, eventType);
+                if (result) {
+                    fail("Event" + eventType
+                            + " was not expected but was received within timeoutMs: " + TIMEOUT_MS);
+                }
+                Thread.sleep(WAIT_TIME_MS);
+            }
+        }
+
         private boolean checkEvent(int userId, int eventType) {
             synchronized (mLock) {
                 for (int i = 0; i < mEvents.size(); i++) {
diff --git a/tests/tests/car/src/android/car/cts/CarTest.java b/tests/tests/car/src/android/car/cts/CarTest.java
index 3c9fff5..b75217d 100644
--- a/tests/tests/car/src/android/car/cts/CarTest.java
+++ b/tests/tests/car/src/android/car/cts/CarTest.java
@@ -17,26 +17,34 @@
 package android.car.cts;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.car.Car;
+import android.car.CarVersion;
+import android.car.PlatformVersion;
+import android.car.test.ApiCheckerRule;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.RequiredFeatureRule;
 
 import org.junit.After;
 import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -52,6 +60,9 @@
     public static final RequiredFeatureRule sRequiredFeatureRule = new RequiredFeatureRule(
             PackageManager.FEATURE_AUTOMOTIVE);
 
+    @Rule
+    public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+
     private static final long DEFAULT_WAIT_TIMEOUT_MS = 2000;
 
     private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -66,6 +77,9 @@
         }
     }
 
+    @ApiTest(apis = {
+            "android.car.Car#isConnected", "android.car.Car#isConnecting", "android.car.Car#connect"
+    })
     @Test
     public void testConnection() throws Exception {
         mServiceConnectionListener = new DefaultServiceConnectionListener();
@@ -81,24 +95,28 @@
         assertThat(mCar.isConnecting()).isFalse();
     }
 
+    @ApiTest(apis = {"android.car.Car#createCar(Context)"})
     @Test
     public void testBlockingCreateCar() throws Exception {
         mCar = Car.createCar(mContext);
         assertConnectedCar(mCar);
     }
 
+    @ApiTest(apis = {"android.car.Car#getCarConnectionType"})
     @Test
     public void testConnectionType() throws Exception {
         createCarAndRunOnReady((car) -> assertThat(car.getCarConnectionType()).isEqualTo(
                 Car.CONNECTION_TYPE_EMBEDDED));
     }
 
+    @ApiTest(apis = {"android.car.Car#isFeatureEnabled(String)"})
     @Test
     public void testIsFeatureEnabled() throws Exception {
         createCarAndRunOnReady(
                 (car) -> assertThat(car.isFeatureEnabled(Car.AUDIO_SERVICE)).isTrue());
     }
 
+    @ApiTest(apis = {"android.car.Car#createCar(Context,Handler,long,CarServiceLifecycleListener)"})
     @Test
     public void testCreateCarWaitForever() throws Exception {
         CarServiceLifecycleListenerImpl listenerImpl = new CarServiceLifecycleListenerImpl(null);
@@ -108,6 +126,7 @@
         listenerImpl.waitForReady(DEFAULT_WAIT_TIMEOUT_MS);
     }
 
+    @ApiTest(apis = {"android.car.Car#createCar(Context,Handler,long,CarServiceLifecycleListener)"})
     @Test
     public void testCreateCarNoWait() throws Exception {
         CarServiceLifecycleListenerImpl listenerImpl = new CarServiceLifecycleListenerImpl(null);
@@ -120,6 +139,8 @@
     }
 
     @Test
+    @ApiTest(apis = {"android.car.Car#isApiVersionAtLeast(int)",
+            "android.car.Car#isApiAndPlatformVersionAtLeast(int,int)"})
     public void testApiVersion() throws Exception {
         int ApiVersionTooHigh = 1000000;
         int MinorApiVersionTooHigh = 1000000;
@@ -144,6 +165,40 @@
                 Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT + 1)).isFalse();
     }
 
+    @ApiTest(apis = {"android.car.Car#getCarVersion"})
+    @Test
+    public void testGetCarVersion() {
+        CarVersion version = Car.getCarVersion();
+
+        assertWithMessage("Car.getCarVersion()").that(version).isNotNull();
+        assertWithMessage("Car.getCarVersion().toString()").that(version.toString())
+                .contains("name=Car.CAR_VERSION");
+    }
+
+    @ApiTest(apis = {"android.car.Car#getPlatformVersion"})
+    @Test
+    public void testGetPlatformVersion() {
+        PlatformVersion version = Car.getPlatformVersion();
+
+        assertWithMessage("Car.getPlatformVersion()").that(version).isNotNull();
+        assertWithMessage("Car.getPlatformVersion().toString()").that(version.toString())
+        .contains("name=Car.PLATFORM_VERSION");
+    }
+
+    @ApiTest(apis = {"android.car.Car#getPlatformVersion"})
+    @Test
+    public void testPlatformVersionIsNotEmulated() {
+        assertSystemPropertyNotSet(Car.PROPERTY_EMULATED_PLATFORM_VERSION_MAJOR);
+        assertSystemPropertyNotSet(Car.PROPERTY_EMULATED_PLATFORM_VERSION_MINOR);
+    }
+
+    private void assertSystemPropertyNotSet(String property) {
+        String value = SystemProperties.get(property);
+        if (!TextUtils.isEmpty(value)) {
+            throw new AssertionError("Property '" + property + "' is set; value is " + value);
+        }
+    }
+
     private static void assertConnectedCar(Car car) {
         assertThat(car).isNotNull();
         assertThat(car.isConnected()).isTrue();
diff --git a/tests/tests/car/src/android/car/cts/CarVersionTest.java b/tests/tests/car/src/android/car/cts/CarVersionTest.java
new file mode 100644
index 0000000..8ce6681
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarVersionTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cts;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.car.CarVersion;
+import android.car.annotation.ApiRequirements;
+import android.os.Parcel;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+
+public final class CarVersionTest extends AbstractCarLessTestCase {
+
+    @Test
+    @ApiTest(apis = {"android.car.CarVersion.VERSION_CODES#TIRAMISU_0"})
+    public void testTiramisu_0() {
+        CarVersion version = CarVersion.VERSION_CODES.TIRAMISU_0;
+
+        assertWithMessage("TIRAMISU_0").that(version).isNotNull();
+        expectWithMessage("TIRAMISU_0.major").that(version.getMajorVersion())
+                .isEqualTo(TIRAMISU);
+        expectWithMessage("TIRAMISU_0.minor").that(version.getMinorVersion())
+                .isEqualTo(0);
+
+        CarVersion fromEnum = ApiRequirements.CarVersion.TIRAMISU_0.get();
+        assertWithMessage("TIRAMISU_0 from enum").that(fromEnum).isNotNull();
+        expectWithMessage("TIRAMISU_0 from enum").that(fromEnum).isSameInstanceAs(version);
+
+        String toString = version.toString();
+        expectWithMessage("TIRAMISU_0.toString()").that(toString)
+                .matches(".*CarVersion.*name=TIRAMISU_0.*major=" + TIRAMISU + ".*minor=0.*");
+        CarVersion clone = clone(version);
+        expectWithMessage("TIRAMISU_0.toString() from parcel").that(clone.toString())
+                .isEqualTo(toString);
+
+        CarVersion anonymous = CarVersion.forMajorAndMinorVersions(version.getMajorVersion(),
+                version.getMinorVersion());
+        expectWithMessage("TIRAMISU_0").that(version).isEqualTo(anonymous);
+        expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+        expectWithMessage("TIRAMISU_0's hashcode").that(version.hashCode())
+                .isEqualTo(anonymous.hashCode());
+        expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+                .isEqualTo(version.hashCode());
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.CarVersion.VERSION_CODES#TIRAMISU_1"})
+    public void testTiramisu_1() {
+        CarVersion version = CarVersion.VERSION_CODES.TIRAMISU_1;
+
+        assertWithMessage("TIRAMISU_1").that(version).isNotNull();
+        expectWithMessage("TIRAMISU_1.major").that(version.getMajorVersion())
+                .isEqualTo(TIRAMISU);
+        expectWithMessage("TIRAMISU_1.minor").that(version.getMinorVersion())
+                .isEqualTo(1);
+
+        CarVersion fromEnum = ApiRequirements.CarVersion.TIRAMISU_1.get();
+        assertWithMessage("TIRAMISU_1 from enum").that(fromEnum).isNotNull();
+        expectWithMessage("TIRAMISU_1 from enum").that(fromEnum).isSameInstanceAs(version);
+
+        String toString = version.toString();
+        expectWithMessage("TIRAMISU_1.toString()").that(toString)
+                .matches(".*CarVersion.*name=TIRAMISU_1.*major=" + TIRAMISU + ".*minor=1.*");
+        CarVersion clone = clone(version);
+        expectWithMessage("TIRAMISU_1.toString() from parcel").that(clone.toString())
+                .isEqualTo(toString);
+
+        CarVersion anonymous = CarVersion.forMajorAndMinorVersions(version.getMajorVersion(),
+                version.getMinorVersion());
+        expectWithMessage("TIRAMISU_1").that(version).isEqualTo(anonymous);
+        expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+        expectWithMessage("TIRAMISU_1's hashcode").that(version.hashCode())
+                .isEqualTo(anonymous.hashCode());
+        expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+                .isEqualTo(version.hashCode());
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.CarVersion.VERSION_CODES#TIRAMISU_2"})
+    public void testTiramisu_2() {
+        CarVersion version = CarVersion.VERSION_CODES.TIRAMISU_2;
+
+        assertWithMessage("TIRAMISU_2").that(version).isNotNull();
+        expectWithMessage("TIRAMISU_2.major").that(version.getMajorVersion())
+                .isEqualTo(TIRAMISU);
+        expectWithMessage("TIRAMISU_2.minor").that(version.getMinorVersion())
+                .isEqualTo(2);
+
+        CarVersion fromEnum = ApiRequirements.CarVersion.TIRAMISU_2.get();
+        assertWithMessage("TIRAMISU_2 from enum").that(fromEnum).isNotNull();
+        expectWithMessage("TIRAMISU_2 from enum").that(fromEnum).isSameInstanceAs(version);
+
+        String toString = version.toString();
+        expectWithMessage("TIRAMISU_2.toString()").that(toString)
+                .matches(".*CarVersion.*name=TIRAMISU_2.*major=" + TIRAMISU + ".*minor=2.*");
+        CarVersion clone = clone(version);
+        expectWithMessage("TIRAMISU_2.toString() from parcel").that(clone.toString())
+                .isEqualTo(toString);
+
+        CarVersion anonymous = CarVersion.forMajorAndMinorVersions(version.getMajorVersion(),
+                version.getMinorVersion());
+        expectWithMessage("TIRAMISU_2").that(version).isEqualTo(anonymous);
+        expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+        expectWithMessage("TIRAMISU_2's hashcode").that(version.hashCode())
+                .isEqualTo(anonymous.hashCode());
+        expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+                .isEqualTo(version.hashCode());
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.CarVersion#CREATOR"})
+    public void testMarshalling() {
+        CarVersion original = CarVersion.forMajorAndMinorVersions(66, 6);
+        expectWithMessage("original.describeContents()").that(original.describeContents())
+                .isEqualTo(0);
+
+        CarVersion clone = clone(original);
+        assertWithMessage("CREATOR.createFromParcel()").that(clone).isNotNull();
+        expectWithMessage("clone.major").that(clone.getMajorVersion()).isEqualTo(66);
+        expectWithMessage("clone.minor").that(clone.getMinorVersion()).isEqualTo(6);
+        expectWithMessage("clone.describeContents()").that(clone.describeContents()).isEqualTo(0);
+        expectWithMessage("clone.equals()").that(clone).isEqualTo(original);
+        expectWithMessage("clone.hashCode()").that(clone.hashCode()).isEqualTo(original.hashCode());
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.CarVersion#CREATOR"})
+    public void testNewArray() {
+        CarVersion[] array = CarVersion.CREATOR.newArray(42);
+
+        assertWithMessage("CREATOR.newArray()").that(array).isNotNull();
+        expectWithMessage("CREATOR.newArray()").that(array).hasLength(42);
+    }
+
+    private CarVersion clone(CarVersion original) {
+        Parcel parcel =  Parcel.obtain();
+        try {
+            original.writeToParcel(parcel, /* flags= */ 0);
+            parcel.setDataPosition(0);
+
+            return CarVersion.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
index 7447cb8..f3c8fc5 100644
--- a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
+++ b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
@@ -16,11 +16,14 @@
 
 package android.car.cts;
 
+import static android.car.PlatformVersion.VERSION_CODES;
+
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.car.Car;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Process;
@@ -162,6 +165,8 @@
      * @return Total written bytes recorded for the current userId and package name.
      */
     private static long parseDump(String content, int userId, String packageName) throws Exception {
+        String ioWritesHeader = Car.getPlatformVersion().isAtLeast(VERSION_CODES.TIRAMISU_1)
+                ? "Top N Storage I/O Writes:" : "Top N Writes:";
         long writtenBytes = 0;
         Section curSection = Section.NONE;
         String errorLines = "";
@@ -177,7 +182,7 @@
             if (line.contains("collector failed to access")) {
                 errorLines += "\n" + line;
             }
-            if (line.matches("Top N Storage I/O Writes:")) {
+            if (line.matches(ioWritesHeader)) {
                 curSection = Section.WRITTEN_BYTES_HEADER_SECTION;
                 continue;
             }
diff --git a/tests/tests/car/src/android/car/cts/PlatformVersionTest.java b/tests/tests/car/src/android/car/cts/PlatformVersionTest.java
new file mode 100644
index 0000000..652c841
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/PlatformVersionTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.car.PlatformVersion;
+import android.car.annotation.ApiRequirements;
+import android.os.Parcel;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+
+public final class PlatformVersionTest extends AbstractCarLessTestCase {
+
+    @Test
+    @ApiTest(apis = {"android.car.PlatformVersion.VERSION_CODES#TIRAMISU_0"})
+    public void testTiramisu_0() {
+        PlatformVersion version = PlatformVersion.VERSION_CODES.TIRAMISU_0;
+
+        assertWithMessage("TIRAMISU_0").that(version).isNotNull();
+        expectWithMessage("TIRAMISU_0.major").that(version.getMajorVersion())
+                .isEqualTo(TIRAMISU);
+        expectWithMessage("TIRAMISU_0.minor").that(version.getMinorVersion())
+                .isEqualTo(0);
+
+        PlatformVersion fromEnum = ApiRequirements.PlatformVersion.TIRAMISU_0.get();
+        assertWithMessage("TIRAMISU_0 from enum").that(fromEnum).isNotNull();
+        expectWithMessage("TIRAMISU_0 from enum").that(fromEnum).isSameInstanceAs(version);
+
+        String toString = version.toString();
+        expectWithMessage("TIRAMISU_0.toString()").that(toString)
+                .matches(".*PlatformVersion.*name=TIRAMISU_0.*major=" + TIRAMISU + ".*minor=0.*");
+        PlatformVersion clone = clone(version);
+        expectWithMessage("TIRAMISU_0.toString() from parcel").that(clone.toString())
+                .isEqualTo(toString);
+
+        PlatformVersion anonymous = PlatformVersion.forMajorAndMinorVersions(
+                version.getMajorVersion(), version.getMinorVersion());
+        expectWithMessage("TIRAMISU_0").that(version).isEqualTo(anonymous);
+        expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+        expectWithMessage("TIRAMISU_0's hashcode").that(version.hashCode())
+                .isEqualTo(anonymous.hashCode());
+        expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+                .isEqualTo(version.hashCode());
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.PlatformVersion.VERSION_CODES#TIRAMISU_1"})
+    public void testTiramisu_1() {
+        PlatformVersion version = PlatformVersion.VERSION_CODES.TIRAMISU_1;
+
+        assertWithMessage("TIRAMISU_1").that(version).isNotNull();
+        expectWithMessage("TIRAMISU_1.major").that(version.getMajorVersion())
+                .isEqualTo(TIRAMISU);
+        expectWithMessage("TIRAMISU_1.minor").that(version.getMinorVersion())
+                .isEqualTo(1);
+
+        PlatformVersion fromEnum = ApiRequirements.PlatformVersion.TIRAMISU_1.get();
+        assertWithMessage("TIRAMISU_1 from enum").that(fromEnum).isNotNull();
+        expectWithMessage("TIRAMISU_1 from enum").that(fromEnum).isSameInstanceAs(version);
+
+        String toString = version.toString();
+        expectWithMessage("TIRAMISU_1.toString()").that(toString)
+                .matches(".*PlatformVersion.*name=TIRAMISU_1.*major=" + TIRAMISU + ".*minor=1.*");
+        PlatformVersion clone = clone(version);
+        expectWithMessage("TIRAMISU_1.toString() from parcel").that(clone.toString())
+                .isEqualTo(toString);
+
+        PlatformVersion anonymous = PlatformVersion.forMajorAndMinorVersions(
+                version.getMajorVersion(), version.getMinorVersion());
+        expectWithMessage("TIRAMISU_1").that(version).isEqualTo(anonymous);
+        expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+        expectWithMessage("TIRAMISU_1's hashcode").that(version.hashCode())
+                .isEqualTo(anonymous.hashCode());
+        expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+                .isEqualTo(version.hashCode());
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.PlatformVersion#CREATOR"})
+    public void testMarshalling() {
+        PlatformVersion original = PlatformVersion.forMajorAndMinorVersions(66, 6);
+        expectWithMessage("original.describeContents()").that(original.describeContents())
+                .isEqualTo(0);
+
+        PlatformVersion clone = clone(original);
+        assertWithMessage("CREATOR.createFromParcel()").that(clone).isNotNull();
+        expectWithMessage("clone.major").that(clone.getMajorVersion()).isEqualTo(66);
+        expectWithMessage("clone.minor").that(clone.getMinorVersion()).isEqualTo(6);
+        expectWithMessage("clone.describeContents()").that(clone.describeContents())
+                .isEqualTo(0);
+        expectWithMessage("clone.equals()").that(clone).isEqualTo(original);
+        expectWithMessage("clone.hashCode()").that(clone.hashCode()).isEqualTo(original.hashCode());
+    }
+
+    @Test
+    @ApiTest(apis = {"android.car.PlatformVersion#CREATOR"})
+    public void testNewArray() {
+        PlatformVersion[] array = PlatformVersion.CREATOR.newArray(42);
+
+        assertWithMessage("CREATOR.newArray()").that(array).isNotNull();
+    }
+
+    private PlatformVersion clone(PlatformVersion original) {
+        Parcel parcel =  Parcel.obtain();
+        try {
+            original.writeToParcel(parcel, /* flags= */ 0);
+            parcel.setDataPosition(0);
+
+            return PlatformVersion.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+}
diff --git a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
index 1e68b01..0001420 100644
--- a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
+++ b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
@@ -173,6 +173,8 @@
                 .isEqualTo("HVAC_MAX_AC_ON");
         assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_MAX_DEFROST_ON))
                 .isEqualTo("HVAC_MAX_DEFROST_ON");
+        assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_ELECTRIC_DEFROSTER_ON))
+                .isEqualTo("HVAC_ELECTRIC_DEFROSTER_ON");
         assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_POWER_ON))
                 .isEqualTo("HVAC_POWER_ON");
         assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_RECIRC_ON))
diff --git a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java
index 743c460..6b1869c 100644
--- a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java
+++ b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java
@@ -20,26 +20,66 @@
 
 import static org.junit.Assume.assumeNotNull;
 
+import android.car.VehicleAreaMirror;
+import android.car.VehicleAreaSeat;
 import android.car.VehicleAreaType;
+import android.car.VehicleAreaWheel;
+import android.car.VehicleAreaWindow;
 import android.car.VehiclePropertyIds;
+import android.car.VehiclePropertyType;
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.property.CarPropertyManager;
 import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 public class VehiclePropertyVerifier<T> {
     private final static String CAR_PROPERTY_VALUE_SOURCE_GETTER = "Getter";
     private final static String CAR_PROPERTY_VALUE_SOURCE_CALLBACK = "Callback";
+    private static final float FLOAT_INEQUALITY_THRESHOLD = 0.00001f;
+    private static final ImmutableSet<Integer> WHEEL_AREAS = ImmutableSet.of(
+            VehicleAreaWheel.WHEEL_LEFT_FRONT, VehicleAreaWheel.WHEEL_LEFT_REAR,
+            VehicleAreaWheel.WHEEL_RIGHT_FRONT, VehicleAreaWheel.WHEEL_RIGHT_REAR);
+    private static final ImmutableSet<Integer> ALL_POSSIBLE_WHEEL_AREA_IDS =
+            generateAllPossibleAreaIds(WHEEL_AREAS);
+    private static final ImmutableSet<Integer> WINDOW_AREAS = ImmutableSet.of(
+            VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD, VehicleAreaWindow.WINDOW_REAR_WINDSHIELD,
+            VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT,
+            VehicleAreaWindow.WINDOW_ROW_2_LEFT, VehicleAreaWindow.WINDOW_ROW_2_RIGHT,
+            VehicleAreaWindow.WINDOW_ROW_3_LEFT, VehicleAreaWindow.WINDOW_ROW_3_RIGHT,
+            VehicleAreaWindow.WINDOW_ROOF_TOP_1, VehicleAreaWindow.WINDOW_ROOF_TOP_2);
+    private static final ImmutableSet<Integer> ALL_POSSIBLE_WINDOW_AREA_IDS =
+            generateAllPossibleAreaIds(WINDOW_AREAS);
+    private static final ImmutableSet<Integer> MIRROR_AREAS = ImmutableSet.of(
+            VehicleAreaMirror.MIRROR_DRIVER_LEFT, VehicleAreaMirror.MIRROR_DRIVER_RIGHT,
+            VehicleAreaMirror.MIRROR_DRIVER_CENTER);
+    private static final ImmutableSet<Integer> ALL_POSSIBLE_MIRROR_AREA_IDS =
+            generateAllPossibleAreaIds(MIRROR_AREAS);
+    private static final ImmutableSet<Integer> SEAT_AREAS = ImmutableSet.of(
+            VehicleAreaSeat.SEAT_ROW_1_LEFT, VehicleAreaSeat.SEAT_ROW_1_CENTER,
+            VehicleAreaSeat.SEAT_ROW_1_RIGHT, VehicleAreaSeat.SEAT_ROW_2_LEFT,
+            VehicleAreaSeat.SEAT_ROW_2_CENTER, VehicleAreaSeat.SEAT_ROW_2_RIGHT,
+            VehicleAreaSeat.SEAT_ROW_3_LEFT, VehicleAreaSeat.SEAT_ROW_3_CENTER,
+            VehicleAreaSeat.SEAT_ROW_3_RIGHT);
+    private static final ImmutableSet<Integer> ALL_POSSIBLE_SEAT_AREA_IDS =
+            generateAllPossibleAreaIds(SEAT_AREAS);
+
 
     private final int mPropertyId;
     private final String mPropertyName;
@@ -51,16 +91,30 @@
     private final Optional<ConfigArrayVerifier> mConfigArrayVerifier;
     private final Optional<CarPropertyValueVerifier> mCarPropertyValueVerifier;
     private final Optional<AreaIdsVerifier> mAreaIdsVerifier;
+    private final Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier;
     private final ImmutableSet<Integer> mPossibleConfigArrayValues;
+    private final ImmutableSet<T> mPossibleCarPropertyValues;
     private final boolean mRequirePropertyValueToBeInConfigArray;
+    private final boolean mVerifySetterWithConfigArrayValues;
+    private final boolean mRequireMinMaxValues;
+    private final boolean mRequireMinValuesToBeZero;
+    private final boolean mRequireZeroToBeContainedInMinMaxRanges;
+    private final boolean mPossiblyDependentOnHvacPowerOn;
 
     private VehiclePropertyVerifier(int propertyId, int access, int areaType, int changeMode,
             Class<T> propertyType, boolean requiredProperty,
             Optional<ConfigArrayVerifier> configArrayVerifier,
             Optional<CarPropertyValueVerifier> carPropertyValueVerifier,
             Optional<AreaIdsVerifier> areaIdsVerifier,
+            Optional<CarPropertyConfigVerifier> carPropertyConfigVerifier,
             ImmutableSet<Integer> possibleConfigArrayValues,
-            boolean requirePropertyValueToBeInConfigArray) {
+            ImmutableSet<T> possibleCarPropertyValues,
+            boolean requirePropertyValueToBeInConfigArray,
+            boolean verifySetterWithConfigArrayValues,
+            boolean requireMinMaxValues,
+            boolean requireMinValuesToBeZero,
+            boolean requireZeroToBeContainedInMinMaxRanges,
+            boolean possiblyDependentOnHvacPowerOn) {
         mPropertyId = propertyId;
         mPropertyName = VehiclePropertyIds.toString(propertyId);
         mAccess = access;
@@ -71,8 +125,15 @@
         mConfigArrayVerifier = configArrayVerifier;
         mCarPropertyValueVerifier = carPropertyValueVerifier;
         mAreaIdsVerifier = areaIdsVerifier;
+        mCarPropertyConfigVerifier = carPropertyConfigVerifier;
         mPossibleConfigArrayValues = possibleConfigArrayValues;
+        mPossibleCarPropertyValues = possibleCarPropertyValues;
         mRequirePropertyValueToBeInConfigArray = requirePropertyValueToBeInConfigArray;
+        mVerifySetterWithConfigArrayValues = verifySetterWithConfigArrayValues;
+        mRequireMinMaxValues = requireMinMaxValues;
+        mRequireMinValuesToBeZero = requireMinValuesToBeZero;
+        mRequireZeroToBeContainedInMinMaxRanges = requireZeroToBeContainedInMinMaxRanges;
+        mPossiblyDependentOnHvacPowerOn = possiblyDependentOnHvacPowerOn;
     }
 
     public static <T> Builder<T> newBuilder(int propertyId, int access, int areaType,
@@ -139,46 +200,160 @@
         }
 
         verifyCarPropertyConfig(carPropertyConfig);
+
+        if (mPossiblyDependentOnHvacPowerOn) {
+            CarPropertyConfig<?> hvacPowerOnCarPropertyConfig =
+                    carPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON);
+            if (hvacPowerOnCarPropertyConfig != null
+                    && hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) {
+                turnOnHvacPower(carPropertyManager,
+                        (CarPropertyConfig<Boolean>) hvacPowerOnCarPropertyConfig);
+            }
+        }
+
         verifyCarPropertyValueGetter(carPropertyConfig, carPropertyManager);
         verifyCarPropertyValueCallback(carPropertyConfig, carPropertyManager);
+        verifyCarPropertyValueSetter(carPropertyConfig, carPropertyManager);
+    }
+
+    private void turnOnHvacPower(CarPropertyManager carPropertyManager,
+            CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig) {
+        for (int areaId : hvacPowerOnCarPropertyConfig.getAreaIds()) {
+            if (carPropertyManager.getProperty(VehiclePropertyIds.HVAC_POWER_ON,
+                    areaId).getValue().equals(true)) {
+                continue;
+            }
+            setPropertyAndWaitForChange(carPropertyManager, VehiclePropertyIds.HVAC_POWER_ON,
+                    hvacPowerOnCarPropertyConfig.getPropertyType(), areaId, Boolean.TRUE);
+        }
+    }
+
+    private void verifyCarPropertyValueSetter(CarPropertyConfig<?> carPropertyConfig,
+            CarPropertyManager carPropertyManager) {
+        if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) {
+            return;
+        }
+        if (Boolean.class.equals(carPropertyConfig.getPropertyType())) {
+            verifyBooleanPropertySetter(carPropertyConfig, carPropertyManager);
+        } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) {
+            verifyIntegerPropertySetter(carPropertyConfig, carPropertyManager);
+        } else if (Float.class.equals(carPropertyConfig.getPropertyType())) {
+            verifyFloatPropertySetter(carPropertyConfig, carPropertyManager);
+        }
+    }
+
+    private void verifyBooleanPropertySetter(CarPropertyConfig<?> carPropertyConfig,
+            CarPropertyManager carPropertyManager) {
+        for (int areaId : carPropertyConfig.getAreaIds()) {
+            CarPropertyValue<Boolean> currentCarPropertyValue = carPropertyManager.getProperty(
+                    mPropertyId, areaId);
+            verifyCarPropertyValue(carPropertyConfig, currentCarPropertyValue, areaId,
+                    CAR_PROPERTY_VALUE_SOURCE_GETTER);
+            Boolean valueToSet = !currentCarPropertyValue.getValue();
+            verifySetProperty((CarPropertyConfig<Boolean>) carPropertyConfig, carPropertyManager,
+                    areaId, valueToSet);
+            verifySetProperty((CarPropertyConfig<Boolean>) carPropertyConfig, carPropertyManager,
+                    areaId, !valueToSet);
+        }
+    }
+
+    private void verifyIntegerPropertySetter(CarPropertyConfig<?> carPropertyConfig,
+            CarPropertyManager carPropertyManager) {
+        if (mVerifySetterWithConfigArrayValues) {
+            verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
+                    (Collection<T>) carPropertyConfig.getConfigArray());
+        } else if (!mPossibleCarPropertyValues.isEmpty()) {
+            verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
+                    mPossibleCarPropertyValues);
+        } else {
+            verifySetterWithMinMaxValues(carPropertyConfig, carPropertyManager);
+        }
+    }
+
+    private void verifySetterWithValues(CarPropertyConfig<T> carPropertyConfig,
+            CarPropertyManager carPropertyManager, Collection<T> valuesToSet) {
+        for (T valueToSet : valuesToSet) {
+            for (int areaId : carPropertyConfig.getAreaIds()) {
+                verifySetProperty(carPropertyConfig, carPropertyManager, areaId, valueToSet);
+            }
+        }
+    }
+
+    private void verifySetterWithMinMaxValues(CarPropertyConfig<?> carPropertyConfig,
+            CarPropertyManager carPropertyManager) {
+        for (int areaId : carPropertyConfig.getAreaIds()) {
+            if (carPropertyConfig.getMinValue(areaId) == null || carPropertyConfig.getMaxValue(
+                    areaId) == null) {
+                continue;
+            }
+            List<Integer> valuesToSet = IntStream.rangeClosed(
+                    ((Integer) carPropertyConfig.getMinValue(areaId)).intValue(),
+                    ((Integer) carPropertyConfig.getMaxValue(areaId)).intValue()).boxed().collect(
+                    Collectors.toList());
+
+            for (Integer valueToSet : valuesToSet) {
+                verifySetProperty((CarPropertyConfig<Integer>) carPropertyConfig,
+                        carPropertyManager, areaId, valueToSet);
+            }
+        }
+    }
+
+    private void verifyFloatPropertySetter(CarPropertyConfig<?> carPropertyConfig,
+            CarPropertyManager carPropertyManager) {
+        if (!mPossibleCarPropertyValues.isEmpty()) {
+            verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
+                    mPossibleCarPropertyValues);
+        }
+    }
+
+    private <T> void verifySetProperty(CarPropertyConfig<T> carPropertyConfig,
+            CarPropertyManager carPropertyManager, int areaId, T valueToSet) {
+        CarPropertyValue<T> currentCarPropertyValue = carPropertyManager.getProperty(mPropertyId,
+                areaId);
+        verifyCarPropertyValue(carPropertyConfig, currentCarPropertyValue, areaId,
+                CAR_PROPERTY_VALUE_SOURCE_GETTER);
+        if (valueEquals(valueToSet, currentCarPropertyValue.getValue())) {
+            return;
+        }
+        CarPropertyValue<T> updatedCarPropertyValue = setPropertyAndWaitForChange(
+                carPropertyManager, mPropertyId, carPropertyConfig.getPropertyType(), areaId,
+                valueToSet);
+        verifyCarPropertyValue(carPropertyConfig, updatedCarPropertyValue, areaId,
+                CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
     }
 
     private void verifyCarPropertyValueCallback(CarPropertyConfig<?> carPropertyConfig,
             CarPropertyManager carPropertyManager) {
-        if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC
-                || mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
-            CarPropertyValueCallback carPropertyValueCallback =
-                    new CarPropertyValueCallback(mPropertyName,
-                            carPropertyConfig.getAreaIds().length);
-            assertWithMessage("Failed to register callback for " + mPropertyName).that(
-                    carPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId,
-                            CarPropertyManager.SENSOR_RATE_ONCHANGE)).isTrue();
-            List<CarPropertyValue<?>> carPropertyValues =
-                    carPropertyValueCallback.getCarPropertyValues();
-            carPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId);
+        if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) {
+            return;
+        }
+        int updatesPerAreaId = 1;
+        long timeoutMillis = 1500;
+        if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
+            updatesPerAreaId = 2;
+            float secondsToMillis = 1_000;
+            long bufferMillis = 1_000; // 1 second
+            timeoutMillis = ((long) ((1.0f / carPropertyConfig.getMinSampleRate()) * secondsToMillis
+                    * updatesPerAreaId)) + bufferMillis;
+        }
 
-            for (CarPropertyValue<?> carPropertyValue : carPropertyValues) {
-                verifyCarPropertyValue(carPropertyConfig, carPropertyValue,
-                        carPropertyValue.getAreaId(), CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
-            }
-            assertWithMessage(mPropertyName
-                    + " callback values did not cover all the property's area IDs").that(
-                    carPropertyValues.stream().map(CarPropertyValue::getAreaId).collect(
-                            Collectors.toList())
-            ).containsExactlyElementsIn(
-                    Arrays.stream(carPropertyConfig.getAreaIds()).boxed().collect(
-                            Collectors.toList()));
+        CarPropertyValueCallback carPropertyValueCallback = new CarPropertyValueCallback(
+                mPropertyName, carPropertyConfig.getAreaIds(), updatesPerAreaId, timeoutMillis);
+        assertWithMessage("Failed to register callback for " + mPropertyName).that(
+                carPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId,
+                        carPropertyConfig.getMaxSampleRate())).isTrue();
+        SparseArray<List<CarPropertyValue<?>>> areaIdToCarPropertyValues =
+                carPropertyValueCallback.getAreaIdToCarPropertyValues();
+        carPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId);
 
-        } else if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
-            CarPropertyValueCallback carPropertyValueCallback =
-                    new CarPropertyValueCallback(mPropertyName, 1);
-            assertWithMessage("Failed to register callback for " + mPropertyName).that(
-                    carPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId,
-                            CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue();
-            List<CarPropertyValue<?>> carPropertyValues =
-                    carPropertyValueCallback.getCarPropertyValues();
-            carPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId);
-
+        for (int areaId : carPropertyConfig.getAreaIds()) {
+            List<CarPropertyValue<?>> carPropertyValues = areaIdToCarPropertyValues.get(areaId);
+            assertWithMessage(
+                    mPropertyName + " callback value list is null for area ID: " + areaId).that(
+                    carPropertyValues).isNotNull();
+            assertWithMessage(mPropertyName + " callback values did not receive " + updatesPerAreaId
+                    + " updates for area ID: " + areaId).that(carPropertyValues.size()).isAtLeast(
+                    updatesPerAreaId);
             for (CarPropertyValue<?> carPropertyValue : carPropertyValues) {
                 verifyCarPropertyValue(carPropertyConfig, carPropertyValue,
                         carPropertyValue.getAreaId(), CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
@@ -212,13 +387,34 @@
         assertWithMessage(mPropertyName + " must be " + mPropertyType + " type property")
                 .that(carPropertyConfig.getPropertyType()).isEqualTo(mPropertyType);
 
-        if (mAreaIdsVerifier.isPresent()) {
-            mAreaIdsVerifier.get().verify(carPropertyConfig.getAreaIds());
-        } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
+        assertWithMessage(mPropertyName + "'s must have at least 1 area ID defined").that(
+                carPropertyConfig.getAreaIds().length).isAtLeast(1);
+        assertWithMessage(mPropertyName + "'s area IDs must all be unique: " + Arrays.toString(
+                carPropertyConfig.getAreaIds())).that(ImmutableSet.copyOf(Arrays.stream(
+                carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList())).size()
+                == carPropertyConfig.getAreaIds().length).isTrue();
+
+        if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
             assertWithMessage(
                     mPropertyName + "'s AreaIds must contain a single 0 since it is "
                             + areaTypeToString(mAreaType))
                     .that(carPropertyConfig.getAreaIds()).isEqualTo(new int[]{0});
+        } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL) {
+            verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_WHEEL_AREA_IDS);
+            verifyNoAreaOverlapInAreaIds(carPropertyConfig, WHEEL_AREAS);
+        } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW) {
+            verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_WINDOW_AREA_IDS);
+            verifyNoAreaOverlapInAreaIds(carPropertyConfig, WINDOW_AREAS);
+        } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR) {
+            verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_MIRROR_AREA_IDS);
+            verifyNoAreaOverlapInAreaIds(carPropertyConfig, MIRROR_AREAS);
+        } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_SEAT
+                && mPropertyId != VehiclePropertyIds.INFO_DRIVER_SEAT) {
+            verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_SEAT_AREA_IDS);
+            verifyNoAreaOverlapInAreaIds(carPropertyConfig, SEAT_AREAS);
+        }
+        if (mAreaIdsVerifier.isPresent()) {
+            mAreaIdsVerifier.get().verify(carPropertyConfig.getAreaIds());
         }
 
         if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
@@ -227,6 +423,9 @@
             verifyNonContinuousCarPropertyConfig(carPropertyConfig);
         }
 
+        mCarPropertyConfigVerifier.ifPresent(carPropertyConfigVerifier ->
+                carPropertyConfigVerifier.verify(carPropertyConfig));
+
         if (!mPossibleConfigArrayValues.isEmpty()) {
             assertWithMessage(
                     mPropertyName + " configArray must specify supported values")
@@ -248,6 +447,62 @@
             assertWithMessage(mPropertyName + " configArray is undefined, so it must be empty")
                     .that(carPropertyConfig.getConfigArray().size()).isEqualTo(0);
         }
+
+        for (int areaId : carPropertyConfig.getAreaIds()) {
+            T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
+            T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
+            if (mRequireMinMaxValues) {
+                assertWithMessage(mPropertyName + " - area ID: " + areaId
+                        + " must have min value defined").that(areaIdMinValue).isNotNull();
+                assertWithMessage(mPropertyName + " - area ID: " + areaId
+                        + " must have max value defined").that(areaIdMaxValue).isNotNull();
+            }
+            if (mRequireMinValuesToBeZero) {
+                assertWithMessage(
+                        mPropertyName + " - area ID: " + areaId + " min value must be zero").that(
+                        areaIdMinValue).isEqualTo(0);
+            }
+            if (mRequireZeroToBeContainedInMinMaxRanges) {
+                assertWithMessage(mPropertyName + " - areaId: " + areaId
+                        + "'s max and min range must contain zero").that(
+                        verifyMaxAndMinRangeContainsZero(areaIdMinValue, areaIdMaxValue)).isTrue();
+
+            }
+            if (areaIdMinValue == null || areaIdMaxValue == null) {
+                continue;
+            }
+            assertWithMessage(
+                    mPropertyName + " - areaId: " + areaId + "'s max value must be >= min value")
+                    .that(verifyMaxAndMin(areaIdMinValue, areaIdMaxValue)).isTrue();
+        }
+    }
+
+    private boolean verifyMaxAndMinRangeContainsZero(T min, T max) {
+        int propertyType = mPropertyId & VehiclePropertyType.MASK;
+        switch (propertyType) {
+            case VehiclePropertyType.INT32:
+                return (Integer) max >= 0 && (Integer) min <= 0;
+            case VehiclePropertyType.INT64:
+                return (Long) max >= 0 && (Long) min <= 0;
+            case VehiclePropertyType.FLOAT:
+                return (Float) max >= 0 && (Float) min <= 0;
+            default:
+                return false;
+        }
+    }
+
+    private boolean verifyMaxAndMin(T min, T max) {
+        int propertyType = mPropertyId & VehiclePropertyType.MASK;
+        switch (propertyType) {
+            case VehiclePropertyType.INT32:
+                return (Integer) max >= (Integer) min;
+            case VehiclePropertyType.INT64:
+                return (Long) max >= (Long) min;
+            case VehiclePropertyType.FLOAT:
+                return (Float) max >= (Float) min;
+            default:
+                return false;
+        }
     }
 
     private void verifyContinuousCarPropertyConfig(CarPropertyConfig<?> carPropertyConfig) {
@@ -276,6 +531,9 @@
 
     private void verifyCarPropertyValueGetter(CarPropertyConfig<?> carPropertyConfig,
             CarPropertyManager carPropertyManager) {
+        if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) {
+            return;
+        }
         for (int areaId : carPropertyConfig.getAreaIds()) {
             CarPropertyValue<?> carPropertyValue =
                     carPropertyManager.getProperty(
@@ -298,6 +556,10 @@
                         + areaId)
                 .that(carPropertyValue.getAreaId())
                 .isEqualTo(areaId);
+        assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source
+                + " area ID must be in carPropertyConfig#getAreaIds()").that(Arrays.stream(
+                carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()).contains(
+                carPropertyValue.getAreaId())).isTrue();
         assertWithMessage(
                 mPropertyName + " - areaId: " + areaId + " - source: " + source
                         + " value's status must be valid")
@@ -326,9 +588,97 @@
                             carPropertyValue.getValue())).isTrue();
         }
 
+        if (!mPossibleCarPropertyValues.isEmpty()) {
+            if (Float.class.equals(mPropertyType)) {
+                boolean foundInPossibleValues = false;
+                for (Float possibleValue : (Collection<Float>) mPossibleCarPropertyValues) {
+                    if (floatEquals(possibleValue, (Float) carPropertyValue.getValue())) {
+                        foundInPossibleValues = true;
+                        break;
+                    }
+                }
+                assertWithMessage(
+                        mPropertyName + " - areaId: " + areaId + " - source: " + source + " value: "
+                                + carPropertyValue.getValue() + " must be listed in the Float set: "
+                                + mPossibleCarPropertyValues).that(foundInPossibleValues).isTrue();
+            } else {
+                assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source
+                        + " value must be listed in the set").that(
+                        carPropertyValue.getValue()).isIn(mPossibleCarPropertyValues);
+            }
+        }
+
         mCarPropertyValueVerifier.ifPresent(
                 propertyValueVerifier -> propertyValueVerifier.verify(carPropertyConfig,
                         carPropertyValue));
+
+        T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
+        T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
+        if (areaIdMinValue != null && areaIdMaxValue != null) {
+            assertWithMessage(
+                "carPropertyValue must be between the max and min values")
+                .that(verifyValueInRange(areaIdMinValue, areaIdMaxValue,
+                    (T) carPropertyValue.getValue())).isTrue();
+        }
+    }
+
+    private boolean verifyValueInRange(T min, T max, T value) {
+        int propertyType = mPropertyId & VehiclePropertyType.MASK;
+        switch (propertyType) {
+            case VehiclePropertyType.INT32:
+                return ((Integer) value >= (Integer) min && (Integer) value <= (Integer) max);
+            case VehiclePropertyType.INT64:
+                return ((Long) value >= (Long) min && (Long) value <= (Long) max);
+            case VehiclePropertyType.FLOAT:
+                return ((Float) value >= (Float) min && (Float) value <= (Float) max);
+            default:
+                return false;
+        }
+    }
+
+    private static ImmutableSet<Integer> generateAllPossibleAreaIds(ImmutableSet<Integer> areas) {
+        ImmutableSet.Builder<Integer> allPossibleAreaIdsBuilder = ImmutableSet.builder();
+        for (int i = 1; i <= areas.size(); i++) {
+            allPossibleAreaIdsBuilder.addAll(Sets.combinations(areas, i).stream().map(areaCombo -> {
+                Integer possibleAreaId = 0;
+                for (Integer area : areaCombo) {
+                    possibleAreaId |= area;
+                }
+                return possibleAreaId;
+            }).collect(Collectors.toList()));
+        }
+        return allPossibleAreaIdsBuilder.build();
+    }
+
+    private void verifyValidAreaIdsForAreaType(CarPropertyConfig<?> carPropertyConfig,
+            ImmutableSet<Integer> allPossibleAreaIds) {
+        for (int areaId : carPropertyConfig.getAreaIds()) {
+            assertWithMessage(
+                    mPropertyName + "'s area ID must be a valid " + areaTypeToString(mAreaType)
+                            + " area ID").that(areaId).isIn(allPossibleAreaIds);
+        }
+    }
+
+    private void verifyNoAreaOverlapInAreaIds(CarPropertyConfig<?> carPropertyConfig,
+            ImmutableSet<Integer> areas) {
+        if (carPropertyConfig.getAreaIds().length < 2) {
+            return;
+        }
+        ImmutableSet<Integer> areaIds = ImmutableSet.copyOf(Arrays.stream(
+                carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()));
+        List<Integer> areaIdOverlapCheckResults = Sets.combinations(areaIds, 2).stream().map(
+                areaIdPair -> {
+                    List<Integer> areaIdPairAsList = areaIdPair.stream().collect(
+                            Collectors.toList());
+                    return areaIdPairAsList.get(0) & areaIdPairAsList.get(1);
+                }).collect(Collectors.toList());
+
+        assertWithMessage(
+                mPropertyName + " area IDs: " + Arrays.toString(carPropertyConfig.getAreaIds())
+                        + " must contain each area only once (e.g. no bitwise AND overlap) for "
+                        + "the area type: " + areaTypeToString(mAreaType)).that(
+                Collections.frequency(areaIdOverlapCheckResults, 0)
+                        == areaIdOverlapCheckResults.size()).isTrue();
     }
 
     public interface ConfigArrayVerifier {
@@ -343,6 +693,10 @@
         void verify(int[] areaIds);
     }
 
+    public interface CarPropertyConfigVerifier {
+        void verify(CarPropertyConfig<?> carPropertyConfig);
+    }
+
     public static class Builder<T> {
         private final int mPropertyId;
         private final int mAccess;
@@ -353,9 +707,15 @@
         private Optional<ConfigArrayVerifier> mConfigArrayVerifier = Optional.empty();
         private Optional<CarPropertyValueVerifier> mCarPropertyValueVerifier = Optional.empty();
         private Optional<AreaIdsVerifier> mAreaIdsVerifier = Optional.empty();
+        private Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier = Optional.empty();
         private ImmutableSet<Integer> mPossibleConfigArrayValues = ImmutableSet.of();
+        private ImmutableSet<T> mPossibleCarPropertyValues = ImmutableSet.of();
         private boolean mRequirePropertyValueToBeInConfigArray = false;
-
+        private boolean mVerifySetterWithConfigArrayValues = false;
+        private boolean mRequireMinMaxValues = false;
+        private boolean mRequireMinValuesToBeZero = false;
+        private boolean mRequireZeroToBeContainedInMinMaxRanges = false;
+        private boolean mPossiblyDependentOnHvacPowerOn = false;
 
         private Builder(int propertyId, int access, int areaType, int changeMode,
                 Class<T> propertyType) {
@@ -387,55 +747,131 @@
             return this;
         }
 
+        public Builder<T> setCarPropertyConfigVerifier(
+                CarPropertyConfigVerifier carPropertyConfigVerifier) {
+            mCarPropertyConfigVerifier = Optional.of(carPropertyConfigVerifier);
+            return this;
+        }
+
         public Builder<T> setPossibleConfigArrayValues(
                 ImmutableSet<Integer> possibleConfigArrayValues) {
             mPossibleConfigArrayValues = possibleConfigArrayValues;
             return this;
         }
 
+        public Builder<T> setPossibleCarPropertyValues(
+                ImmutableSet<T> possibleCarPropertyValues) {
+            mPossibleCarPropertyValues = possibleCarPropertyValues;
+            return this;
+        }
+
         public Builder<T> requirePropertyValueTobeInConfigArray() {
             mRequirePropertyValueToBeInConfigArray = true;
             return this;
         }
 
+        public Builder<T> verifySetterWithConfigArrayValues() {
+            mVerifySetterWithConfigArrayValues = true;
+            return this;
+        }
+
+        public Builder<T> requireMinMaxValues() {
+            mRequireMinMaxValues = true;
+            return this;
+        }
+
+        public Builder<T> requireMinValuesToBeZero() {
+            mRequireMinValuesToBeZero = true;
+            return this;
+        }
+
+        public Builder<T> requireZeroToBeContainedInMinMaxRanges() {
+            mRequireZeroToBeContainedInMinMaxRanges = true;
+            return this;
+        }
+
+        public Builder<T> setPossiblyDependentOnHvacPowerOn() {
+            mPossiblyDependentOnHvacPowerOn = true;
+            return this;
+        }
+
         public VehiclePropertyVerifier<T> build() {
             return new VehiclePropertyVerifier<>(mPropertyId, mAccess, mAreaType, mChangeMode,
                     mPropertyType, mRequiredProperty, mConfigArrayVerifier,
-                    mCarPropertyValueVerifier, mAreaIdsVerifier, mPossibleConfigArrayValues,
-                    mRequirePropertyValueToBeInConfigArray);
+                    mCarPropertyValueVerifier, mAreaIdsVerifier, mCarPropertyConfigVerifier,
+                    mPossibleConfigArrayValues, mPossibleCarPropertyValues,
+                    mRequirePropertyValueToBeInConfigArray, mVerifySetterWithConfigArrayValues,
+                    mRequireMinMaxValues, mRequireMinValuesToBeZero,
+                    mRequireZeroToBeContainedInMinMaxRanges, mPossiblyDependentOnHvacPowerOn);
         }
     }
 
     private static class CarPropertyValueCallback implements
             CarPropertyManager.CarPropertyEventCallback {
         private final String mPropertyName;
-        private final int mTotalOnChangeEvents;
-        private final CountDownLatch mCountDownLatch;
-        private final List<CarPropertyValue<?>> mCarPropertyValues = new ArrayList<>();
+        private final int[] mAreaIds;
+        private final int mTotalCarPropertyValuesPerAreaId;
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+        private final Object mLock = new Object();
+        @GuardedBy("mLock")
+        private final SparseArray<List<CarPropertyValue<?>>> mAreaIdToCarPropertyValues =
+                new SparseArray<>();
+        private final long mTimeoutMillis;
 
-        public CarPropertyValueCallback(String propertyName, int totalOnChangeEvents) {
+        CarPropertyValueCallback(String propertyName, int[] areaIds,
+                int totalCarPropertyValuesPerAreaId, long timeoutMillis) {
             mPropertyName = propertyName;
-            mTotalOnChangeEvents = totalOnChangeEvents;
-            mCountDownLatch = new CountDownLatch(totalOnChangeEvents);
+            mAreaIds = areaIds;
+            mTotalCarPropertyValuesPerAreaId = totalCarPropertyValuesPerAreaId;
+            mTimeoutMillis = timeoutMillis;
+            synchronized (mLock) {
+                for (int areaId : mAreaIds) {
+                    mAreaIdToCarPropertyValues.put(areaId, new ArrayList<>());
+                }
+            }
         }
 
-        public List<CarPropertyValue<?>> getCarPropertyValues() {
+        public SparseArray<List<CarPropertyValue<?>>> getAreaIdToCarPropertyValues() {
+            boolean awaitSuccess = false;
             try {
-                assertWithMessage(
-                        "Never received " + mTotalOnChangeEvents + "  onChangeEvent(s) for "
-                                + mPropertyName + " callback before 1500ms timeout").that(
-                        mCountDownLatch.await(1500, TimeUnit.MILLISECONDS)).isTrue();
+                awaitSuccess = mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS);
             } catch (InterruptedException e) {
                 assertWithMessage("Waiting for onChangeEvent callback(s) for " + mPropertyName
                         + " threw an exception: " + e).fail();
             }
-            return mCarPropertyValues;
+            synchronized (mLock) {
+                assertWithMessage("Never received " + mTotalCarPropertyValuesPerAreaId
+                        + "  CarPropertyValues for all " + mPropertyName + "'s areaIds: "
+                        + Arrays.toString(mAreaIds) + " before " + mTimeoutMillis + " ms timeout - "
+                        + mAreaIdToCarPropertyValues).that(awaitSuccess).isTrue();
+                return mAreaIdToCarPropertyValues.clone();
+            }
         }
 
         @Override
-        public void onChangeEvent(CarPropertyValue value) {
-            mCarPropertyValues.add(value);
-            mCountDownLatch.countDown();
+        public void onChangeEvent(CarPropertyValue carPropertyValue) {
+            synchronized (mLock) {
+                if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
+                    return;
+                }
+                mAreaIdToCarPropertyValues.get(carPropertyValue.getAreaId()).add(carPropertyValue);
+                if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
+                    mCountDownLatch.countDown();
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        private boolean hasEnoughCarPropertyValuesForEachAreaIdLocked() {
+            for (int areaId : mAreaIds) {
+                List<CarPropertyValue<?>> carPropertyValues = mAreaIdToCarPropertyValues.get(
+                        areaId);
+                if (carPropertyValues == null
+                        || carPropertyValues.size() < mTotalCarPropertyValuesPerAreaId) {
+                    return false;
+                }
+            }
+            return true;
         }
 
         @Override
@@ -446,4 +882,74 @@
         public void onErrorEvent(int propId, int areaId, int errorCode) {
         }
     }
+
+
+    private static class SetterCallback<T> implements CarPropertyManager.CarPropertyEventCallback {
+        private final int mPropertyId;
+        private final String mPropertyName;
+        private final int mAreaId;
+        private final T mExpectedSetValue;
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+        private final long mCreationTimeNanos = SystemClock.elapsedRealtimeNanos();
+        private CarPropertyValue<?> mUpdatedCarPropertyValue = null;
+
+        SetterCallback(int propertyId, int areaId, T expectedSetValue) {
+            mPropertyId = propertyId;
+            mPropertyName = VehiclePropertyIds.toString(propertyId);
+            mAreaId = areaId;
+            mExpectedSetValue = expectedSetValue;
+        }
+
+        public CarPropertyValue<?> waitForUpdatedCarPropertyValue() {
+            try {
+                assertWithMessage(
+                        "Never received onChangeEvent(s) for " + mPropertyName + " new value: "
+                                + mExpectedSetValue + " before 5s timeout").that(
+                        mCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                assertWithMessage("Waiting for onChangeEvent set callback for " + mPropertyName
+                        + " threw an exception: " + e).fail();
+            }
+            return mUpdatedCarPropertyValue;
+        }
+
+        @Override
+        public void onChangeEvent(CarPropertyValue carPropertyValue) {
+            if (mUpdatedCarPropertyValue != null || carPropertyValue.getPropertyId() != mPropertyId
+                    || carPropertyValue.getAreaId() != mAreaId
+                    || carPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE
+                    || carPropertyValue.getTimestamp() <= mCreationTimeNanos
+                    || carPropertyValue.getTimestamp() >= SystemClock.elapsedRealtimeNanos()
+                    || !valueEquals(mExpectedSetValue, (T) carPropertyValue.getValue())) {
+                return;
+            }
+            mUpdatedCarPropertyValue = carPropertyValue;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+        }
+    }
+
+    private static <V> boolean valueEquals(V v1, V v2) {
+        return (v1 instanceof Float && floatEquals((Float) v1, (Float) v2)) || v1.equals(v2);
+    }
+
+    private static boolean floatEquals(float f1, float f2) {
+        return Math.abs(f1 - f2) < FLOAT_INEQUALITY_THRESHOLD;
+    }
+
+    private static <U> CarPropertyValue<U> setPropertyAndWaitForChange(
+            CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType,
+            int areaId, U valueToSet) {
+        SetterCallback setterCallback = new SetterCallback(propertyId, areaId, valueToSet);
+        assertWithMessage("Failed to register setter callback for " + VehiclePropertyIds.toString(
+                propertyId)).that(carPropertyManager.registerCallback(setterCallback, propertyId,
+                CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue();
+        carPropertyManager.setProperty(propertyType, propertyId, areaId, valueToSet);
+        CarPropertyValue<U> carPropertyValue = setterCallback.waitForUpdatedCarPropertyValue();
+        carPropertyManager.unregisterCallback(setterCallback, propertyId);
+        return carPropertyValue;
+    }
 }
diff --git a/tests/tests/car_builtin/OWNERS b/tests/tests/car_builtin/OWNERS
index a8e927d..bda2116 100644
--- a/tests/tests/car_builtin/OWNERS
+++ b/tests/tests/car_builtin/OWNERS
@@ -1,4 +1,2 @@
 # Bug component: 526266
-keunyoung@google.com
-felipeal@google.com
-gurunagarajan@google.com
+include platform/packages/services/Car:/OWNERS
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
index 95a3d18..582772d 100644
--- a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
@@ -48,7 +48,6 @@
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -88,6 +87,8 @@
     // ActivityManager.getRunningAppProcess called in isAppRunning needs this permission
     private static final String PERMISSION_INTERACT_ACROSS_USERS_FULL =
             "android.permission.INTERACT_ACROSS_USERS_FULL";
+    private static final String PERMISSION_REAL_GET_TASKS =
+            "android.permission.REAL_GET_TASKS";
 
     private static final String SIMPLE_APP_PACKAGE_NAME = "android.car.cts.builtin.apps.simple";
     private static final String SIMPLE_ACTIVITY_RELATIVE_NAME = ".SimpleActivity";
@@ -183,8 +184,7 @@
         ComponentName activityName = task1TopActivity.getComponentName();
         waitAndAssertTopResumedActivity(activityName, DEFAULT_DISPLAY,
                 "Activity must be resumed");
-        assertWithMessage("task1 top activity has focus")
-                .that(task1TopActivity.hasFocus()).isTrue();
+        waitAndAssertFocusStatusChanged(task1TopActivity, true);
         assertWithMessage("task1 top activity is visible")
                 .that(task1TopActivity.isVisible()).isTrue();
 
@@ -269,7 +269,6 @@
                 .isEqualTo(expectedLaunchAllowed);
     }
 
-    @Ignore("b/232432706")
     @Test
     public void testStopAllTasksForUser() throws Exception {
         int initialCurrentUserId = getCurrentUserId();
@@ -279,6 +278,7 @@
             mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
                     PERMISSION_MANAGE_ACTIVITY_TASKS,
                     PERMISSION_REMOVE_TASKS,
+                    PERMISSION_REAL_GET_TASKS,
                     PERMISSION_INTERACT_ACROSS_USERS_FULL);
 
             testUserId = createUser(CTS_CAR_TEST_USER_NAME);
@@ -290,15 +290,13 @@
             installPackageForUser(testUserId);
 
             launchSimpleActivityInCurrentUser();
-            waitUntilSimpleActivityExistenceStatusIs(true);
-            assertThat(isAppRunning(SIMPLE_APP_PACKAGE_NAME)).isTrue();
+            assertIsAppRunning(true, SIMPLE_APP_PACKAGE_NAME);
 
             switchUser(initialCurrentUserId);
             waitUntilUserCurrent(initialCurrentUserId);
 
             stopAllTasksForUser(testUserId);
-            waitUntilSimpleActivityExistenceStatusIs(false);
-            assertThat(isAppRunning(SIMPLE_APP_PACKAGE_NAME)).isFalse();
+            assertIsAppRunning(false, SIMPLE_APP_PACKAGE_NAME);
 
             removeUser(testUserId);
             testUserId = INVALID_USER_ID;
@@ -377,14 +375,17 @@
         Log.d(TAG, "installPackageForUser return: " + retStr);
     }
 
+    private void assertIsAppRunning(boolean isRunning, String pkgName) {
+        PollingCheck.waitFor(TIMEOUT_MS, () -> isAppRunning(pkgName) == isRunning);
+    }
+
     private boolean isAppRunning(String pkgName) {
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
-        List<ActivityManager.RunningAppProcessInfo> runningAppProcesses =
-                am.getRunningAppProcesses();
+        List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(MAX_NUM_TASKS);
 
-        for (ActivityManager.RunningAppProcessInfo procInfo : runningAppProcesses) {
-            if (pkgName.equals(procInfo.processName)) {
+        for (ActivityManager.RunningTaskInfo taskInfo : runningTasks) {
+            if (pkgName.equals(taskInfo.baseActivity.getPackageName())) {
                 return true;
             }
         }
@@ -444,6 +445,11 @@
     public static final class ActivityC extends ActivityManagerTestActivityBase {
     }
 
+    private static void waitAndAssertFocusStatusChanged(ActivityManagerTestActivityBase activity,
+            boolean expectedStatus) throws Exception {
+        PollingCheck.waitFor(TIMEOUT_MS, () -> activity.hasFocus() == expectedStatus);
+    }
+
     private static int createUser(String userName) throws Exception {
         Log.d(TAG, "createUser: " + userName);
         String retStr = SystemUtil.runShellCommand(CREATE_USER_COMMAND + userName);
@@ -496,11 +502,6 @@
         ActivityManagerHelper.stopAllTasksForUser(userId);
     }
 
-    private static void waitUntilSimpleActivityExistenceStatusIs(boolean expectedStatus) {
-        PollingCheck.waitFor(TIMEOUT_MS,
-                () -> (checkSimpleActivityExistence() == expectedStatus));
-    }
-
     private static boolean checkSimpleActivityExistence() {
         boolean foundSimpleActivity = false;
 
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
index 36c1217..de74595 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
@@ -390,6 +390,34 @@
     }
 
     @Test
+    public void testRequestNetworkScanWithRenounceWithoutChannels() {
+        boolean isLocationSwitchOn = getAndSetLocationSwitch(true);
+        try {
+            mNetworkScanRequest = buildNetworkScanRequest(/*includeBandsAndChannels=*/false);
+            mNetworkScanCallback = new NetworkScanCallbackImpl();
+            Message startNetworkScan = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RENOUNCE_START,
+                    false);
+            setReady(false);
+            startNetworkScan.sendToTarget();
+            waitUntilReady();
+
+            Log.d(TAG, "mNetworkScanStatus: " + mNetworkScanStatus);
+            assertWithMessage(
+                    "The final scan status is "
+                            + mNetworkScanStatus
+                            + " with error code "
+                            + mErrorCode
+                            + ", not ScanCompleted"
+                            + " or ScanError with an error code ERROR_MODEM_UNAVAILABLE or"
+                            + " ERROR_UNSUPPORTED")
+                    .that(isScanStatusValid())
+                    .isTrue();
+        } finally {
+            getAndSetLocationSwitch(isLocationSwitchOn);
+        }
+    }
+
+    @Test
     public void testRequestNetworkScanLocationOffPass() {
         requestNetworkScanLocationOffHelper(false, true);
     }
diff --git a/tests/tests/content/IntentResolutionTestApp/Android.bp b/tests/tests/content/IntentResolutionTestApp/Android.bp
index 16159cf..79c7538 100644
--- a/tests/tests/content/IntentResolutionTestApp/Android.bp
+++ b/tests/tests/content/IntentResolutionTestApp/Android.bp
@@ -16,9 +16,9 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-android_test_helper_app {
-    name: "CtsIntentResolutionTestApp",
-    defaults: ["cts_defaults"],
+java_defaults {
+    name: "intent_test_app_defaults",
+    srcs: ["src/**/*.java"],
     sdk_version: "current",
     // tag this module as a cts test artifact
     test_suites: [
@@ -28,17 +28,22 @@
     min_sdk_version: "29",
 }
 
+android_test_helper_app {
+    name: "CtsIntentResolutionTestApp",
+    defaults: [
+        "cts_defaults",
+        "intent_test_app_defaults",
+    ],
+    additional_manifests: ["provider.xml"],
+}
+
 // Same app but with lower target SDK version and different package_name
 android_test_helper_app {
     name: "CtsIntentResolutionTestAppApi30",
-    package_name: "android.content.cts.IntentResolutionTestApi30",
-    defaults: ["cts_defaults"],
-    sdk_version: "current",
-    target_sdk_version: "30",
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
+    defaults: [
+        "cts_defaults",
+        "intent_test_app_defaults",
     ],
-    min_sdk_version: "29",
+    package_name: "android.content.cts.IntentResolutionTestApi30",
+    target_sdk_version: "30",
 }
diff --git a/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml b/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
index ac50cb1..1c0aa1b 100644
--- a/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
+++ b/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
@@ -16,7 +16,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="android.content.cts.IntentResolutionTest" >
-    <application android:hasCode="false">
+    <application>
         <activity android:name="android.content.pm.cts.TestPmActivity"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/content/IntentResolutionTestApp/provider.xml b/tests/tests/content/IntentResolutionTestApp/provider.xml
new file mode 100644
index 0000000..a8774b6
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/provider.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.content.cts.IntentResolutionTest" >
+    <application>
+        <provider android:name="android.content.pm.cts.TestProvider"
+              android:exported="true"
+              android:authorities="${applicationId}.provider" />
+    </application>
+</manifest>
diff --git a/tests/tests/content/IntentResolutionTestApp/src/android/content/pm/cts/TestProvider.java b/tests/tests/content/IntentResolutionTestApp/src/android/content/pm/cts/TestProvider.java
new file mode 100644
index 0000000..ae22d56
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/src/android/content/pm/cts/TestProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.cts;
+
+import android.app.PendingIntent;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+public class TestProvider extends ContentProvider {
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        var intent = new Intent()
+                .setClassName(getContext(), "android.content.pm.cts.TestPmActivity")
+                .setAction("non.existing.action");
+        var p = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE);
+        var b = new Bundle();
+        b.putParcelable("pendingIntent", p);
+        return b;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index b742cc9..cd6f3d6 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -16,6 +16,7 @@
 
 package android.content.cts;
 
+import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
@@ -151,7 +152,9 @@
 
         mRegisteredReceiverList = new ArrayList<BroadcastReceiver>();
 
-        mOriginalWallpaper = (BitmapDrawable) mContext.getWallpaper();
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mOriginalWallpaper = (BitmapDrawable) mContext.getWallpaper(),
+                READ_WALLPAPER_INTERNAL);
     }
 
     @Override
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index b429278..8b27e9a 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -16,6 +16,7 @@
 
 package android.content.pm.cts;
 
+import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
 import static android.Manifest.permission.INSTALL_TEST_ONLY_PACKAGE;
 import static android.content.pm.ApplicationInfo.FLAG_HAS_CODE;
 import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
@@ -41,8 +42,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import com.google.common.truth.Expect;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -56,6 +55,8 @@
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -99,14 +100,16 @@
 import android.util.Log;
 
 import androidx.core.content.FileProvider;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ServiceTestRule;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.TestUtils;
 
+import com.google.common.truth.Expect;
+
 import junit.framework.AssertionFailedError;
 
 import org.junit.After;
@@ -240,9 +243,9 @@
 
     @Before
     public void setup() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-        mPackageManager = mContext.getPackageManager();
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getContext();
+        mPackageManager = mContext.getPackageManager();
     }
 
     @After
@@ -312,8 +315,8 @@
         checkProviderInfoName(PROVIDER_NAME, providers);
     }
 
-    @Test
-    public void testEnforceIntentToMatchIntentFilter() {
+    // Disable the test due to feature revert
+    private void testEnforceIntentToMatchIntentFilter() {
         Intent intent = new Intent();
         List<ResolveInfo> results;
 
@@ -505,6 +508,34 @@
         results = mPackageManager.queryBroadcastReceivers(intent,
                 PackageManager.ResolveInfoFlags.of(0));
         assertEquals(0, results.size());
+
+        /* Pending Intent tests */
+
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(GET_INTENT_SENDER_INTENT);
+        var authority = INTENT_RESOLUTION_TEST_PKG_NAME + ".provider";
+        Bundle b = mContext.getContentResolver().call(authority, "", null, null);
+        assertNotNull(b);
+        PendingIntent pi = b.getParcelable("pendingIntent", PendingIntent.class);
+        assertNotNull(pi);
+        intent = pi.getIntent();
+        // It should be a non-matching intent, which cannot be resolved in our package
+        results = mPackageManager.queryIntentActivities(intent,
+            PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        // However, querying on behalf of the pending intent creator should work properly
+        results = pi.queryIntentComponents(0);
+        assertEquals(1, results.size());
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+
+        intent = new Intent();
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setComponent(
+                new ComponentName("android", "com.android.internal.app.ResolverActivity"));
+        try {
+            mContext.startActivity(intent);
+        } catch (ActivityNotFoundException ignore) {
+
+        }
     }
 
     private void checkActivityInfoName(String expectedName, List<ResolveInfo> resolves) {
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
index f5b3cf5..a4ebe91 100644
--- a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
@@ -43,5 +43,14 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </service>
+        <service
+            android:name=".TestSystemDreamService"
+            android:exported="true"
+            android:permission="android.permission.BIND_DREAM_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.dreams.DreamService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </service>
     </application>
 </manifest>
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestSystemDreamService.java b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestSystemDreamService.java
new file mode 100644
index 0000000..57f4cf8
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestSystemDreamService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.dream.cts.app;
+
+import android.graphics.Color;
+import android.service.dreams.DreamService;
+import android.widget.FrameLayout;
+
+/**
+ * {@link TestSystemDreamService} is a minimal concrete {@link DreamService} implementation that
+ * sets the entire window to be red.
+ */
+public class TestSystemDreamService extends DreamService {
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        setInteractive(false);
+        setFullscreen(true);
+
+        final FrameLayout frameLayout = new FrameLayout(getApplicationContext());
+        frameLayout.setBackgroundColor(Color.RED);
+        setContentView(frameLayout);
+    }
+}
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
index 8c836fa..88b50de 100644
--- a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
@@ -99,7 +99,7 @@
                 ComponentName.unflattenFromString(DREAM_SERVICE_COMPONENT);
         final ComponentName dreamActivity = mDreamCoordinator.setActiveDream(dreamService);
 
-        mDreamCoordinator.startDream(dreamService);
+        mDreamCoordinator.startDream();
         waitAndAssertTopResumedActivity(dreamActivity, Display.DEFAULT_DISPLAY,
                 "Dream activity should be the top resumed activity");
         // Wait on count down latch.
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
index 166227a..23831ef 100644
--- a/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
@@ -74,7 +74,7 @@
                 ComponentName.unflattenFromString(DREAM_SERVICE_COMPONENT);
         final ComponentName dreamActivity = mDreamCoordinator.setActiveDream(dreamService);
 
-        mDreamCoordinator.startDream(dreamService);
+        mDreamCoordinator.startDream();
         waitAndAssertTopResumedActivity(dreamActivity, Display.DEFAULT_DISPLAY,
                 "Dream activity should be the top resumed activity");
         mDreamCoordinator.stopDream();
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/SystemDreamTest.java b/tests/tests/dreams/src/android/service/dreams/cts/SystemDreamTest.java
new file mode 100644
index 0000000..3a65645
--- /dev/null
+++ b/tests/tests/dreams/src/android/service/dreams/cts/SystemDreamTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams.cts;
+
+import android.content.ComponentName;
+import android.server.wm.ActivityManagerTestBase;
+import android.server.wm.DreamCoordinator;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@ApiTest(apis = {"com.android.server.dreams.DreamManagerService#setSystemDreamComponent"})
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SystemDreamTest extends ActivityManagerTestBase {
+    private static final String USER_DREAM_COMPONENT =
+            "android.app.dream.cts.app/.TestDreamService";
+    private static final String SYSTEM_DREAM_COMPONENT =
+            "android.app.dream.cts.app/.TestSystemDreamService";
+
+    private final DreamCoordinator mDreamCoordinator = new DreamCoordinator(mContext);
+
+    private ComponentName mSystemDream;
+    private ComponentName mUserDreamActivity;
+
+    @Before
+    public void setup() {
+        mDreamCoordinator.setup();
+
+        final ComponentName userDream = ComponentName.unflattenFromString(USER_DREAM_COMPONENT);
+        mSystemDream = ComponentName.unflattenFromString(SYSTEM_DREAM_COMPONENT);
+        mUserDreamActivity = mDreamCoordinator.setActiveDream(userDream);
+    }
+
+    @After
+    public void reset()  {
+        mDreamCoordinator.restoreDefaults();
+    }
+
+    @Test
+    public void startDream_systemDreamNotSet_startUserDream() {
+        startAndVerifyDreamActivity(mUserDreamActivity);
+    }
+
+    @Test
+    public void startDream_systemDreamSet_startSystemDream() {
+        final ComponentName systemDreamActivity = mDreamCoordinator.setSystemDream(mSystemDream);
+        startAndVerifyDreamActivity(systemDreamActivity);
+    }
+
+    @Test
+    public void switchDream_systemDreamSet_switchToSystemDream() {
+        mDreamCoordinator.startDream();
+
+        // Sets system dream.
+        final ComponentName systemDreamActivity = mDreamCoordinator.setSystemDream(mSystemDream);
+        try {
+            // Verifies switched to system dream.
+            waitAndAssertTopResumedActivity(systemDreamActivity, Display.DEFAULT_DISPLAY,
+                    getDreamActivityVerificationMessage(systemDreamActivity));
+        } finally {
+            mDreamCoordinator.stopDream();
+        }
+    }
+
+    @Test
+    public void switchDream_systemDreamCleared_switchToUserDream() {
+        mDreamCoordinator.setSystemDream(mSystemDream);
+        mDreamCoordinator.startDream();
+
+        // Clears system dream.
+        mDreamCoordinator.setSystemDream(null);
+        try {
+            // Verifies switched back to user dream.
+            waitAndAssertTopResumedActivity(mUserDreamActivity, Display.DEFAULT_DISPLAY,
+                    getDreamActivityVerificationMessage(mUserDreamActivity));
+        } finally {
+            mDreamCoordinator.stopDream();
+        }
+    }
+
+    private void startAndVerifyDreamActivity(ComponentName expectedDreamActivity) {
+        try {
+            mDreamCoordinator.startDream();
+            waitAndAssertTopResumedActivity(expectedDreamActivity, Display.DEFAULT_DISPLAY,
+                    getDreamActivityVerificationMessage(expectedDreamActivity));
+        } finally {
+            mDreamCoordinator.stopDream();
+        }
+    }
+
+    private String getDreamActivityVerificationMessage(ComponentName activity) {
+        return activity.flattenToString() + " should be displayed";
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java b/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java
index b21edd5..3b92c16 100644
--- a/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java
@@ -31,6 +31,7 @@
 import junit.framework.Assert;
 
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,10 +54,17 @@
     boolean mFinitePaused = false;
     boolean mFinitePausedSet = false;
     boolean mResumed = false;
+    long mDefaultAnimatorPauseDelay = 10000L;
+
+    @Before
+    public void setup() {
+        mDefaultAnimatorPauseDelay = Animator.getBackgroundPauseDelay();
+    }
 
     @After
     public void cleanup() {
         Animator.setAnimatorPausingEnabled(true);
+        Animator.setBackgroundPauseDelay(mDefaultAnimatorPauseDelay);
     }
 
     /**
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java
index 36d0237..097a9a3 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java
@@ -73,6 +73,7 @@
                 .build());
         // Convert the input axis size to its equivalent fraction of the total screen.
         final float computedSize = inputSize / (DISPLAY_WIDTH - 1f);
+
         // There's an extraneous ACTION_HOVER_ENTER event in builds before T QPR1. We fixed the
         // related bug (b/244744917) so the hover event is not generated anymore. In order to make
         // the test permissive to existing and new behaviors we only verify the first 2 events here.
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
index c9588f4..acfd627 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
@@ -107,7 +107,7 @@
     private final static String TAG = "AudioManagerTest";
 
     private final static long ASYNC_TIMING_TOLERANCE_MS = 50;
-    private final static long POLL_TIME_VOLUME_ADJUST = 400;
+    private static final long POLL_TIME_VOLUME_ADJUST = 400;
     private final static long POLL_TIME_UPDATE_INTERRUPTION_FILTER = 5000;
     private final static int MP3_TO_PLAY = R.raw.testmp3; // ~ 5 second mp3
     private final static long POLL_TIME_PLAY_MUSIC = 2000;
diff --git a/tests/tests/media/misc/Android.bp b/tests/tests/media/misc/Android.bp
index feaabe4..87208db 100644
--- a/tests/tests/media/misc/Android.bp
+++ b/tests/tests/media/misc/Android.bp
@@ -66,6 +66,7 @@
         "junit-params",
         "testng",
         "truth-prebuilt",
+        "Harrier",
         "mockito-target-minus-junit4",
         "androidx.heifwriter_heifwriter",
         "CtsCameraUtils",
diff --git a/tests/tests/media/misc/AndroidTest.xml b/tests/tests/media/misc/AndroidTest.xml
index 4a2ffaf..4ecf8fd 100644
--- a/tests/tests/media/misc/AndroidTest.xml
+++ b/tests/tests/media/misc/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="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="multiuser" />
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
         <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
         <option name="set-test-harness" value="false" />
@@ -57,5 +58,6 @@
         <option name="hidden-api-checks" value="false" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
     </test>
 </configuration>
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
index 89d336c..aae07c7 100644
--- a/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
@@ -56,12 +56,17 @@
 import android.text.TextUtils;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.UserTest;
 import com.android.compatibility.common.util.PollingCheck;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -75,12 +80,16 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(BedsteadJUnit4.class)
 @AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
 @LargeTest
 @NonMediaMainlineTest
 public class MediaRouter2Test {
     private static final String TAG = "MR2Test";
+
+    // Required by Bedstead.
+    @ClassRule @Rule public static final DeviceState sDeviceState = new DeviceState();
+
     Context mContext;
     private MediaRouter2 mRouter2;
     private Executor mExecutor;
@@ -158,8 +167,11 @@
     }
 
     /**
-     * Tests if we get proper routes for application that has special route type.
+     * Tests if we get proper routes for an application that requests a special route type.
+     *
+     * <p>Runs on both the primary user and a work profile, as per {@link UserTest}.
      */
+    @UserTest({UserType.PRIMARY_USER, UserType.WORK_PROFILE})
     @Test
     public void testGetRoutes() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
index 6d4fede..7ab5239 100644
--- a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
@@ -166,10 +166,33 @@
         assertTrue(keyEventSessionListener.mCountDownLatch
                 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertNull(keyEventSessionListener.mSessionToken);
+        assertEquals("", keyEventSessionListener.mPackageName);
+
         assertNull(mSessionManager.getMediaKeyEventSession());
         assertEquals("", mSessionManager.getMediaKeyEventSessionPackageName());
     }
 
+    public void testOnMediaKeyEventSessionChangedListener_noSession_passesEmptyPackageAndNullToken()
+            throws InterruptedException {
+        // The permission can be held only on S+
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.MEDIA_CONTENT_CONTROL);
+
+        MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
+        mSessionManager.addOnMediaKeyEventSessionChangedListener(
+                Executors.newSingleThreadExecutor(), keyEventSessionListener);
+
+        assertTrue(keyEventSessionListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertNull(keyEventSessionListener.mSessionToken);
+        assertEquals("", keyEventSessionListener.mPackageName);
+
+        // Clean up listener.
+        mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener);
+    }
+
     private MediaSession createMediaKeySession() {
         MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
         session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
@@ -749,6 +772,7 @@
             implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
         CountDownLatch mCountDownLatch;
         MediaSession.Token mSessionToken;
+        String mPackageName;
 
         MediaKeyEventSessionListener() {
             mCountDownLatch = new CountDownLatch(1);
@@ -757,12 +781,15 @@
         void resetCountDownLatch() {
             mCountDownLatch = new CountDownLatch(1);
             mSessionToken = null;
+            mPackageName = null;
         }
 
         @Override
         public void onMediaKeyEventSessionChanged(String packageName,
                 MediaSession.Token sessionToken) {
+            assertNotNull("The package name cannot be null.", packageName);
             mSessionToken = sessionToken;
+            mPackageName = packageName;
             mCountDownLatch.countDown();
         }
     }
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index 0a109a4..a2aebb5 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -27,6 +27,7 @@
 import static junit.framework.TestCase.assertNotNull;
 
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 
@@ -117,9 +118,9 @@
                 REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
                 REVOKE_RUNTIME_PERMISSIONS);
         if (mNotificationListenerService != null) mNotificationListenerService.resetData();
+        if (mNotificationAssistantService != null) mNotificationAssistantService.resetData();
 
-        toggleListenerAccess(false);
-        toggleAssistantAccess(false);
+        disconnectListeners();
         mUi.dropShellPermissionIdentity();
     }
 
@@ -510,9 +511,10 @@
         assertTrue(String.format("snoozed notification <%s> was not removed", sbn.getKey()),
                 mNotificationListenerService.checkRemovedKey(sbn.getKey()));
 
-        assertEquals(String.format("snoozed notification <%s> was not observed by NAS", sbn.getKey()),
-                sbn.getKey(), mNotificationAssistantService.snoozedKey);
+        assertEquals(String.format("snoozed notification <%s> was not observed by NAS",
+                        sbn.getKey()), sbn.getKey(), mNotificationAssistantService.snoozedKey);
         assertEquals(snoozeContext, mNotificationAssistantService.snoozedUntilContext);
+        mNotificationAssistantService.unsnoozeNotification(sbn.getKey());
     }
 
     @Test
@@ -554,7 +556,8 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(
+                mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
         Notification.Action action = new Notification.Action.Builder(null, "",
                 pendingIntent).build();
         // This method has to exist and the call cannot fail
@@ -672,7 +675,8 @@
 
         setUpListeners();
         turnScreenOn();
-        mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+        mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE",
+                "android.permission.EXPAND_STATUS_BAR");
 
         mNotificationAssistantService.resetNotificationClickCount();
 
@@ -697,7 +701,8 @@
         assumeFalse("Status bar service not supported", isWatch());
 
         setUpListeners(); // also enables assistant
-        mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+        mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE",
+                "android.permission.EXPAND_STATUS_BAR");
 
         sendNotification(1, ICON_ID);
         StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
@@ -752,6 +757,18 @@
         assertNotNull(mNotificationAssistantService);
     }
 
+    private void disconnectListeners() throws Exception {
+        toggleListenerAccess(false);
+        toggleAssistantAccess(false);
+        Thread.sleep(2 * SLEEP_TIME); // wait for listener and assistant to be disconnected
+
+        mNotificationListenerService = TestNotificationListener.getInstance();
+        mNotificationAssistantService = TestNotificationAssistant.getInstance();
+
+        assertNull(mNotificationListenerService);
+        assertNull(mNotificationAssistantService);
+    }
+
     private void sendNotification(final int id, final int icon) throws Exception {
         sendNotification(id, null, icon);
     }
@@ -763,7 +780,8 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(
+                mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
@@ -780,7 +798,8 @@
         Person person = new Person.Builder()
                 .setName("test")
                 .build();
-        final Notification notification = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+        final Notification notification = new Notification.Builder(
+                mContext, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("foo")
                 .setShortcutId("shareShortcut")
                 .setStyle(new Notification.MessagingStyle(person)
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
index b9f063e..a53c93f 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
@@ -74,6 +74,18 @@
     @Override
     public void onListenerDisconnected() {
         isConnected = false;
+        sNotificationAssistantInstance = null;
+    }
+
+    public void resetData() {
+        resetNotificationClickCount();
+        resetNotificationVisibilityCounts();
+        isPanelOpen = false;
+        snoozedKey = null;
+        snoozedUntilContext = null;
+        notificationRank = -1;
+        notificationFeedback = 0;
+        mRemoved.clear();
     }
 
     public static TestNotificationAssistant getInstance() {
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
index a7f068c..9d0337d 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
@@ -70,6 +70,7 @@
     @Override
     public void onListenerDisconnected() {
         isConnected = false;
+        sNotificationListenerInstance = null;
     }
 
     public static TestNotificationListener getInstance() {
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index c495afa..01b2580 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -3847,7 +3847,7 @@
          <p>Should only be requested by the System, should be required by
          TileService declarations.-->
     <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- Allows SystemUI to request third party controls.
          <p>Should only be requested by the System and required by
@@ -6038,7 +6038,7 @@
     <!-- @SystemApi Allows to access all app shortcuts.
          @hide -->
     <permission android:name="android.permission.ACCESS_SHORTCUTS"
-        android:protectionLevel="signature|role" />
+        android:protectionLevel="signature|role|recents" />
 
     <!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
          @hide -->
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 576df28..93d9f29 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -20,8 +20,6 @@
 import static android.content.pm.PermissionInfo.PROTECTION_MASK_BASE;
 import static android.os.Build.VERSION.SECURITY_PATCH;
 
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.Manifest;
@@ -31,7 +29,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
-import android.os.Build;
 import android.os.Process;
 import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
@@ -77,16 +74,9 @@
     private static final String SET_UNRESTRICTED_GESTURE_EXCLUSION
             = "android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION";
 
-    private static final String BIND_OEM_CAR_SERVICE =
-            "android.car.permission.BIND_OEM_CAR_SERVICE";
-
     private static final String RECEIVE_KEYCODE_EVENTS_PERMISSION =
             "android.permission.RECEIVE_KEYCODE_EVENTS";
 
-    private static final String ACCESS_SHORTCUTS_PERMISSION = "android.permission.ACCESS_SHORTCUTS";
-    private static final String BIND_QUICK_SETTINGS_TILE =
-            "android.permission.BIND_QUICK_SETTINGS_TILE";
-
     private static final String LOG_TAG = "PermissionProtectionTest";
 
     private static final String PLATFORM_PACKAGE_NAME = "android";
@@ -232,11 +222,7 @@
             final int expectedProtectionFlags =
                     expectedPermission.protectionLevel & ~PROTECTION_MASK_BASE;
             final int declaredProtectionFlags = declaredPermission.getProtectionFlags();
-            if (expectedProtectionFlags != declaredProtectionFlags
-                    && !shouldAllowProtectionFlagsChange(
-                            expectedPermissionName,
-                            expectedProtectionFlags,
-                            declaredProtectionFlags)) {
+            if (expectedProtectionFlags != declaredProtectionFlags) {
                 offendingList.add(
                         String.format(
                                 "Permission %s invalid enforced protection %x, expected %x",
@@ -544,8 +530,6 @@
                 return parseDate(SECURITY_PATCH).before(MANAGE_COMPANION_DEVICES_PATCH_DATE);
             case SET_UNRESTRICTED_GESTURE_EXCLUSION:
                 return true;
-            case BIND_OEM_CAR_SERVICE:
-                return shoudldSkipBindOemCarService();
             case RECEIVE_KEYCODE_EVENTS_PERMISSION:
                 return true;
             default:
@@ -553,29 +537,6 @@
         }
     }
 
-    /**
-     * check should be skipped only for T and T-QPR1
-     */
-    private boolean shoudldSkipBindOemCarService() {
-        if (Build.VERSION.SDK_INT > 33) {
-            return false;
-        }
-        String output = runShellCommand("dumpsys car_service --version");
-        if (output.contains("Car API minor: 0") || output.contains("Car API minor: 1")) {
-            return true;
-        }
-
-        return false;
-    }
-
-    private static boolean shouldAllowProtectionFlagsChange(
-            String permissionName, int expectedFlags, int actualFlags) {
-        return (ACCESS_SHORTCUTS_PERMISSION.equals(permissionName)
-                        || BIND_QUICK_SETTINGS_TILE.equals(permissionName))
-                && ((expectedFlags | PermissionInfo.PROTECTION_FLAG_RECENTS)
-                        == (actualFlags | PermissionInfo.PROTECTION_FLAG_RECENTS));
-    }
-
     private class ExpectedPermissionInfo {
         final @NonNull String name;
         final @Nullable String group;
diff --git a/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java b/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java
index eaddc09..ce31901 100644
--- a/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java
@@ -31,6 +31,7 @@
 
 import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 6a15b65..989edca 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -33,6 +33,7 @@
         "compatibility-common-util-devicesidelib",
         "guava",
         "platform-test-annotations",
+        "permission-test-util-lib",
         "sts-device-util",
         "hamcrest-library",
         "NeneInternal",
@@ -80,6 +81,16 @@
         ":CtsDeviceInfo",
         ":RolePermissionOverrideTestApp",
         ":SplitBluetoothPermissionTestApp",
+        ":CtsPermissionBackupAppCert1",
+        ":CtsPermissionBackupAppCert1Dup",
+        ":CtsPermissionBackupAppCert2",
+        ":CtsPermissionBackupAppCert3",
+        ":CtsPermissionBackupAppCert4",
+        ":CtsPermissionBackupAppCert12",
+        ":CtsPermissionBackupAppCert12Dup",
+        ":CtsPermissionBackupAppCert34",
+        ":CtsPermissionBackupAppCert123",
+        ":CtsPermissionBackupAppCert4History124",
         ":CtsAppThatRequestCustomCameraPermission",
     ],
 }
@@ -90,6 +101,115 @@
     manifest: "testdata/packageinstallertestapp.xml",
 }
 
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert1",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-1",
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert1Dup",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-1",
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert2",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-2",
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert3",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-3",
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert4",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-4",
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert12",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-1",
+    additional_certificates: [
+        ":permission-test-cert-2",
+    ],
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert12Dup",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-1",
+    additional_certificates: [
+        ":permission-test-cert-2",
+    ],
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert34",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-3",
+    additional_certificates: [
+        ":permission-test-cert-4",
+    ],
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert123",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-1",
+    additional_certificates: [
+        ":permission-test-cert-2",
+        ":permission-test-cert-3",
+    ],
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsPermissionBackupAppCert4History124",
+    min_sdk_version: "30",
+    resource_dirs: [],
+    asset_dirs: [],
+    certificate: ":permission-test-cert-4",
+    additional_certificates: [
+        ":permission-test-cert-1",
+
+    ],
+    rotationMinSdkVersion: "30",
+    lineage: ":permission-test-cert-with-rotation-history",
+    manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
 android_app_certificate {
     name: "security_cts_test_certificate",
     certificate: "security_cts_test_cert",
@@ -97,5 +217,34 @@
 
 android_test_helper_app {
     name: "RolePermissionOverrideTestApp",
+    resource_dirs: [],
+    asset_dirs: [],
     manifest: "testdata/rolepermissionoverridetestapp.xml",
 }
+
+android_app_certificate {
+    name: "permission-test-cert-1",
+    certificate: "test-cert-1",
+}
+
+android_app_certificate {
+    name: "permission-test-cert-2",
+    certificate: "test-cert-2",
+}
+
+android_app_certificate {
+    name: "permission-test-cert-3",
+    certificate: "test-cert-3",
+}
+
+android_app_certificate {
+    name: "permission-test-cert-4",
+    certificate: "test-cert-4",
+}
+
+filegroup {
+    name: "permission-test-cert-with-rotation-history",
+    srcs: [
+        "test-cert-with-1-2-4-in-rotation-history",
+    ],
+}
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 4e43d57..f05a623 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -31,6 +31,9 @@
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+
     <uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
 
     <!-- For FileIntegrityManager -->
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 57ffd67..6a6dc92 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -52,6 +52,16 @@
         <option name="cleanup" value="true" />
         <option name="push" value="RolePermissionOverrideTestApp.apk->/data/local/tmp/cts/security/RolePermissionOverrideTestApp.apk" />
         <option name="push" value="SplitBluetoothPermissionTestApp.apk->/data/local/tmp/cts/security/SplitBluetoothPermissionTestApp.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert1.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert1.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert1Dup.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert1Dup.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert2.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert2.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert3.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert3.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert4.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert4.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert12.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert12.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert12Dup.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert12Dup.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert123.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert123.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert34.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert34.apk" />
+        <option name="push" value="CtsPermissionBackupAppCert4History124.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert4History124.apk" />
         <option name="push" value="CtsAppThatRequestCustomCameraPermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestCustomCameraPermission.apk" />
     </target_preparer>
 
@@ -62,4 +72,9 @@
         <option name="test-timeout" value="900000" />
         <option name="hidden-api-checks" value="false" />
     </test>
+
+    <target_preparer class="android.cts.backup.BackupPreparer">
+        <option name="enable-backup-if-needed" value="true" />
+        <option name="select-local-transport" value="true" />
+    </target_preparer>
 </configuration>
diff --git a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
index a27a0b9..116d21e 100644
--- a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
@@ -22,15 +22,25 @@
 
 import static org.junit.Assert.*;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.Application;
+
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+
+import android.os.Process;
+import android.os.UserHandle;
 import android.platform.test.annotations.AsbSecurityTest;
 import android.util.Log;
 import android.view.SurfaceControl;
@@ -43,8 +53,11 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.SystemUtil;
+
+import com.android.compatibility.common.util.ShellUtils;
 import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -56,6 +69,80 @@
 @RunWith(AndroidJUnit4.class)
 public class ActivityManagerTest extends StsExtraBusinessLogicTestCase {
 
+    private boolean canSupportMultiuser() {
+        String output = ShellUtils.runShellCommand("pm get-max-users");
+        if (output.contains("Maximum supported users:")) {
+            return Integer.parseInt(output.split(": ", 2)[1].trim()) > 1;
+        }
+        return false;
+    }
+
+    @AsbSecurityTest(cveBugId = 217934898)
+    @Test
+    public void testActivityManager_registerUidChangeObserver_onlyNoInteractAcrossPermission()
+            throws Exception {
+        if (!canSupportMultiuser()) {
+            return;
+        }
+        String out = "";
+        final UidImportanceObserver observer = new UidImportanceObserver();
+        final ActivityManager mActMan = (ActivityManager) getContext()
+                .getSystemService(Context.ACTIVITY_SERVICE);
+        final int currentUser = mActMan.getCurrentUser();
+        try {
+
+            mActMan.addOnUidImportanceListener(observer,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+
+            out = ShellUtils.runShellCommand("pm create-user testUser");
+            out = out.length() > 2 ? out.substring(out.length() - 2) : out;
+
+            ShellUtils.runShellCommand("am switch-user " + out);
+
+            Thread.sleep(5000);
+            assertFalse(observer.didObserverOtherUser());
+        } finally {
+            ShellUtils.runShellCommand("am switch-user " + currentUser);
+            ShellUtils.runShellCommand("pm remove-user " + out);
+            mActMan.removeOnUidImportanceListener(observer);
+        }
+    }
+
+    @AsbSecurityTest(cveBugId = 217934898)
+    @Test
+    public void testActivityManager_registerUidChangeObserver_allPermission()
+            throws Exception {
+        if (!canSupportMultiuser()) {
+            return;
+        }
+        String out = "";
+        final UidImportanceObserver observer = new UidImportanceObserver();
+        final ActivityManager mActMan = (ActivityManager) getContext()
+                .getSystemService(Context.ACTIVITY_SERVICE);
+        final int currentUser = mActMan.getCurrentUser();
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation
+                .adoptShellPermissionIdentity("android.permission.INTERACT_ACROSS_USERS_FULL");
+        try {
+            mActMan.addOnUidImportanceListener(observer,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+
+            out = ShellUtils.runShellCommand("pm create-user testUser");
+            out = out.length() > 2 ? out.substring(out.length() - 2) : out;
+
+            ShellUtils.runShellCommand("am switch-user " + out);
+
+            Thread.sleep(5000);
+            assertTrue(observer.didObserverOtherUser());
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+            ShellUtils.runShellCommand("am switch-user " + currentUser);
+            ShellUtils.runShellCommand("pm remove-user " + out);
+            mActMan.removeOnUidImportanceListener(observer);
+        }
+    }
+
     @AsbSecurityTest(cveBugId = 19394591)
     @Test
     public void testActivityManager_injectInputEvents() throws ClassNotFoundException {
@@ -290,4 +377,26 @@
             }
         }
     }
+
+    static final class UidImportanceObserver implements ActivityManager.OnUidImportanceListener {
+
+        private boolean mObservedNonOwned = false;
+        private int mMyUid;
+
+        UidImportanceObserver() {
+            mMyUid = UserHandle.getUserId(Process.myUid());
+        }
+
+        public void onUidImportance(int uid, int importance) {
+            Log.i("ActivityManagerTestObserver", "Observing change for "
+                    + uid + " by user " + UserHandle.getUserId(uid));
+            if (UserHandle.getUserId(uid) != mMyUid) {
+                mObservedNonOwned = true;
+            }
+        }
+
+        public boolean didObserverOtherUser() {
+            return this.mObservedNonOwned;
+        }
+    }
 }
diff --git a/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java b/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java
index c6b7e35..487c3d9 100644
--- a/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java
+++ b/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java
@@ -86,7 +86,8 @@
                 List<String> bypassedCheckOps = new ArrayList<>();
                 for (PackageInfo pi : pkgs) {
                     ApplicationInfo ai = pi.applicationInfo;
-                    if (ai.uid != Process.SYSTEM_UID) {
+                    int appId = UserHandle.getAppId(ai.uid);
+                    if (appId != Process.SYSTEM_UID) {
                         final int[] mode = {MODE_ALLOWED};
                         runWithShellPermissionIdentity(() -> {
                             mode[0] = mAom.noteOpNoThrow(
diff --git a/tests/tests/security/src/android/security/cts/PermissionBackupCertificateCheckTest.kt b/tests/tests/security/src/android/security/cts/PermissionBackupCertificateCheckTest.kt
new file mode 100644
index 0000000..1170939
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/PermissionBackupCertificateCheckTest.kt
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2018 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.security.cts
+
+import android.Manifest.permission.*
+import android.app.AppOpsManager
+import android.content.pm.PackageManager.*
+import android.os.ParcelFileDescriptor
+import android.permission.cts.PermissionUtils.grantPermission
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.AsbSecurityTest
+import androidx.test.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compatibility.common.util.BackupUtils
+import com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN
+import com.android.compatibility.common.util.BusinessLogicTestCase
+import com.android.compatibility.common.util.ShellUtils.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase
+import java.io.InputStream
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests that permissions for backed up apps are restored only after checking that their signing
+ * certificates are compared.
+ *
+ * @see [com.android.permissioncontroller.permission.service.BackupHelper]
+ */
+@AppModeFull
+@RunWith(AndroidJUnit4::class)
+class PermissionBackupCertificateCheckTest : StsExtraBusinessLogicTestCase() {
+    private val backupUtils: BackupUtils =
+        object : BackupUtils() {
+            override fun executeShellCommand(command: String): InputStream {
+                val pfd =
+                    BusinessLogicTestCase.getInstrumentation()
+                        .uiAutomation
+                        .executeShellCommand(command)
+                return ParcelFileDescriptor.AutoCloseInputStream(pfd)
+            }
+        }
+
+    private var isBackupSupported = false
+
+    private val targetContext = InstrumentationRegistry.getTargetContext()
+
+    @Before
+    fun setUp() {
+        val packageManager = BusinessLogicTestCase.getInstrumentation().context.packageManager
+        isBackupSupported =
+            (packageManager != null && packageManager.hasSystemFeature(FEATURE_BACKUP))
+
+        if (isBackupSupported) {
+            assertTrue("Backup not enabled", backupUtils.isBackupEnabled)
+            assertTrue("LocalTransport not selected", backupUtils.isLocalTransportSelected)
+            backupUtils.executeShellCommandSync("setprop log.tag.$APP_LOG_TAG VERBOSE")
+        }
+    }
+
+    @After
+    fun tearDown() {
+        uninstallIfInstalled(APP)
+        clearFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET)
+        clearFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has the
+     * same certificate as the backed up app.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_sameCert_restoresRuntimePermissions() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_1_DUP)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has a
+     * different certificate as the backed up app.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_diffCert_doesNotGrantRuntimePermissions() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_3)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has the
+     * backed up app's certificate in its signing history.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_midHistoryToRotated_restoresRuntimePermissions() {
+        install(APP_APK_CERT_2)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has the
+     * backed up app's certificate as the original certificate in its signing history.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_origToRotated_restoresRuntimePermissions() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the backed up app has the
+     * restored app's certificate in its signing history.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_rotatedToMidHistory_restoresRuntimePermissions() {
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_2)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the backed up app has the
+     * restored app's certificate in its signing history as its original certificate.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_rotatedToOrig_restoresRuntimePermissions() {
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_1)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the backed up app has the same
+     * certificate as the restored app, but the restored app additionally has signing certificate
+     * history.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_sameWithHistory_restoresRuntimePermissions() {
+        install(APP_APK_CERT_4)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the backed up app has the same
+     * certificate as the restored app, but the backed up app additionally has signing certificate
+     * history.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_sameWithoutHistory_restoresRuntimePermissions() {
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has
+     * signing history, but the backed up app's certificate is not in this signing history.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_notInBackedUpHistory_doesNotRestoreRuntimePerms() {
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_3)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has
+     * signing history, but the backed up app's certificate is not in this signing history.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_notInRestoredHistory_doesNotRestoreRuntimePerms() {
+        install(APP_APK_CERT_3)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has
+     * multiple certificates, and the backed up app also has identical multiple certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_sameMultCerts_restoresRuntimePermissions() {
+        install(APP_APK_CERT_1_2)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_1_2_DUP)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has
+     * multiple certificates, and the backed up app do not have identical multiple certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_diffMultCerts_doesNotRestoreRuntimePermissions() {
+        install(APP_APK_CERT_1_2)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_3_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the app being restored has
+     * multiple certificates, and the backed up app's certificate is present in th restored app's
+     * certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_singleToMultiCert_restoresRuntimePerms() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_1_2_3)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the backed up app and the app
+     * being restored have multiple certificates, and the backed up app's certificates are a subset
+     * of the restored app's certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_multCertsToSuperset_doesNotRestoreRuntimePerms() {
+        install(APP_APK_CERT_1_2)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_1_2_3)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of regular runtime permissions, when the backed up app and the app
+     * being restored have multiple certificates, and the backed up app's certificates are a
+     * superset of the restored app's certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_multCertsToSubset_doesNotRestoreRuntimePermissions() {
+        install(APP_APK_CERT_1_2_3)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_1_2)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+        }
+    }
+
+    /**
+     * Test backup and restore of tri-state permissions, when both foreground and background runtime
+     * permissions are not granted and the backed up and restored app have compatible certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_fgBgDenied_matchingCerts_restoresFgBgPermissions() {
+        install(APP_APK_CERT_2)
+        if (!isBackupSupported) {
+            return
+        }
+        // Make a token change to permission state, to enable to us to determine when restore is
+        // complete.
+        grantPermission(APP, WRITE_CONTACTS)
+        // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+        // to ensure that permissions are backed up.
+        setFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET)
+        setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+
+            // Wait until restore is complete.
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, WRITE_CONTACTS))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+            assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+        }
+    }
+
+    /**
+     * Test backup and restore of tri-state permissions, when both foreground and background runtime
+     * permissions are not granted and the backed up and restored app don't have compatible
+     * certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_fgBgDenied_notMatchingCerts_doesNotRestorePerms() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        // Make a token change to permission state, to enable to us to determine when restore is
+        // complete.
+        grantPermission(APP, WRITE_CONTACTS)
+        // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+        // to ensure that permissions are backed up.
+        setFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET)
+        setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_2)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+
+            // Wait until restore is complete.
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, WRITE_CONTACTS))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+            assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+        }
+    }
+
+    /**
+     * Test backup and restore of tri-state permissions, when foreground runtime permission is
+     * granted and the backed up and restored app have compatible certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_fgGranted_matchingCerts_restoresFgBgPermissions() {
+        install(APP_APK_CERT_2)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+        // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+        // to ensure that permissions are backed up.
+        setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+            assertEquals(AppOpsManager.MODE_FOREGROUND, getAppOp(APP, ACCESS_FINE_LOCATION))
+        }
+    }
+
+    /**
+     * Test backup and restore of tri-state permissions, when foreground runtime permission is
+     * granted and the backed up and restored app don't have compatible certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_fgGranted_notMatchingCerts_doesNotRestoreFgBgPerms() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+        // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+        // to ensure that permissions are backed up.
+        setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_2)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+            assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+        }
+    }
+
+    /**
+     * Test backup and restore of tri-state permissions, when foreground and background runtime
+     * permissions are granted and the backed up and restored app have compatible certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_fgBgGranted_matchingCerts_restoresFgBgPermissions() {
+        install(APP_APK_CERT_2)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+        grantPermission(APP, ACCESS_BACKGROUND_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+            assertEquals(AppOpsManager.MODE_ALLOWED, getAppOp(APP, ACCESS_FINE_LOCATION))
+        }
+    }
+
+    /**
+     * Test backup and restore of tri-state permissions, when foreground and background runtime
+     * permissions are granted and the backed up and restored app don't have compatible
+     * certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_fgBgGranted_notMatchingCerts_restoresFgBgPerms() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+        grantPermission(APP, ACCESS_BACKGROUND_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_2)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually {
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+            assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+            assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+        }
+    }
+
+    /**
+     * Test backup and restore of flags when the backed up app and restored app have compatible
+     * certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_matchingCerts_restoresFlags() {
+        install(APP_APK_CERT_2)
+        if (!isBackupSupported) {
+            return
+        }
+        setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually { assertTrue(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)) }
+    }
+
+    /**
+     * Test backup and restore of flags when the backed up app and restored app don't have
+     * compatible certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_notMatchingCerts_doesNotRestoreFlag() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        install(APP_APK_CERT_2)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+        eventually { assertFalse(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)) }
+    }
+
+    /**
+     * Test backup and delayed restore of regular runtime permission, i.e. when an app is installed
+     * after restore has run, and the backed up app and restored app have compatible certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_appInstalledLater_matchingCerts_restoresCorrectly() {
+        install(APP_APK_CERT_2)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+
+        eventually { assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION)) }
+    }
+
+    /**
+     * Test backup and delayed restore of regular runtime permission, i.e. when an app is installed
+     * after restore has run, and the backed up app and restored app don't have compatible
+     * certificates.
+     */
+    @Test
+    @AsbSecurityTest(cveBugId = [184847040])
+    fun testRestore_appInstalledLater_notMatchingCerts_doesNotRestore() {
+        install(APP_APK_CERT_1)
+        if (!isBackupSupported) {
+            return
+        }
+        grantPermission(APP, ACCESS_FINE_LOCATION)
+
+        backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+        uninstallIfInstalled(APP)
+        backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+        install(APP_APK_CERT_4_HISTORY_1_2_4)
+
+        eventually { assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION)) }
+    }
+
+    private fun install(apk: String) {
+        val output = runShellCommand("pm install -r $apk")
+        assertEquals("Success", output)
+    }
+
+    private fun uninstallIfInstalled(packageName: String) {
+        runShellCommand("pm uninstall $packageName")
+    }
+
+    private fun setFlag(app: String, permission: String, flag: Int) {
+        runWithShellPermissionIdentity {
+            targetContext.packageManager.updatePermissionFlags(
+                permission, app, flag, flag, targetContext.user)
+        }
+    }
+
+    private fun clearFlag(app: String, permission: String, flag: Int) {
+        runWithShellPermissionIdentity {
+            targetContext.packageManager.updatePermissionFlags(
+                permission, app, flag, 0, targetContext.user)
+        }
+    }
+
+    private fun isFlagSet(app: String, permission: String, flag: Int): Boolean {
+        return try {
+            callWithShellPermissionIdentity<Int> {
+                targetContext.packageManager.getPermissionFlags(permission, app, targetContext.user)
+            } and flag == flag
+        } catch (e: Exception) {
+            throw RuntimeException(e)
+        }
+    }
+
+    private fun checkPermission(app: String, permission: String): Int {
+        return targetContext.packageManager.checkPermission(permission, app)
+    }
+
+    private fun getAppOp(app: String, permission: String): Int {
+        return try {
+            callWithShellPermissionIdentity {
+                targetContext
+                    .getSystemService<AppOpsManager>(AppOpsManager::class.java)!!
+                    .unsafeCheckOpRaw(
+                        AppOpsManager.permissionToOp(permission)!!,
+                        targetContext.packageManager.getPackageUid(app, 0),
+                        app)
+            }
+        } catch (e: Exception) {
+            throw RuntimeException(e)
+        }
+    }
+
+    companion object {
+        /** The name of the package of the apps under test */
+        private const val APP = "android.security.permissionbackup"
+        /** The apk of the packages */
+        private const val APK_PATH = "/data/local/tmp/cts/security/"
+        private const val APP_APK_CERT_1 = "${APK_PATH}CtsPermissionBackupAppCert1.apk"
+        private const val APP_APK_CERT_1_DUP = "${APK_PATH}CtsPermissionBackupAppCert1Dup.apk"
+        private const val APP_APK_CERT_2 = "${APK_PATH}CtsPermissionBackupAppCert2.apk"
+        private const val APP_APK_CERT_3 = "${APK_PATH}CtsPermissionBackupAppCert3.apk"
+        private const val APP_APK_CERT_4 = "${APK_PATH}CtsPermissionBackupAppCert4.apk"
+        private const val APP_APK_CERT_1_2 = "${APK_PATH}CtsPermissionBackupAppCert12.apk"
+        private const val APP_APK_CERT_1_2_DUP = "${APK_PATH}CtsPermissionBackupAppCert12Dup.apk"
+        private const val APP_APK_CERT_1_2_3 = "${APK_PATH}CtsPermissionBackupAppCert123.apk"
+        private const val APP_APK_CERT_3_4 = "${APK_PATH}CtsPermissionBackupAppCert34.apk"
+        private const val APP_APK_CERT_4_HISTORY_1_2_4 =
+            "${APK_PATH}CtsPermissionBackupAppCert4History124.apk"
+        private const val APP_LOG_TAG = "PermissionBackupApp"
+        /** The name of the package for backup */
+        private const val ANDROID_PACKAGE = "android"
+        private const val TIMEOUT_MILLIS: Long = 10000
+
+        /**
+         * Make sure that a [Runnable] eventually finishes without throwing an [Exception].
+         *
+         * @param r The [Runnable] to run.
+         */
+        fun eventually(r: Runnable) {
+            val start = System.currentTimeMillis()
+            while (true) {
+                try {
+                    r.run()
+                    return
+                } catch (e: Throwable) {
+                    if (System.currentTimeMillis() - start < TIMEOUT_MILLIS) {
+                        try {
+                            Thread.sleep(100)
+                        } catch (ignored: InterruptedException) {
+                            throw RuntimeException(e)
+                        }
+                    } else {
+                        throw e
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/security/test-cert-1.pk8 b/tests/tests/security/test-cert-1.pk8
new file mode 100644
index 0000000..f781c30
--- /dev/null
+++ b/tests/tests/security/test-cert-1.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-1.x509.pem b/tests/tests/security/test-cert-1.x509.pem
new file mode 100644
index 0000000..06adcfe
--- /dev/null
+++ b/tests/tests/security/test-cert-1.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbDCCARGgAwIBAgIJAMoPtk37ZudyMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM
+B2VjLXAyNTYwHhcNMTYwMzMxMTQ1ODA2WhcNNDMwODE3MTQ1ODA2WjASMRAwDgYD
+VQQDDAdlYy1wMjU2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpl8RPSLLSROQ
+gwesMe4roOkTi3hfrGU20U6izpDStL/hlLUM3I4Wn1SnOpke8Pp2MpglvgeMx4J0
+BwPaRLTX66NQME4wHQYDVR0OBBYEFNQTNWi5WzAVizIgceqMQ/9bBczIMB8GA1Ud
+IwQYMBaAFNQTNWi5WzAVizIgceqMQ/9bBczIMAwGA1UdEwQFMAMBAf8wCgYIKoZI
+zj0EAwIDSQAwRgIhAPUEoIZsrvAp9BcULFy3E1THn/zR1kBhjfyk8Z4W23jWAiEA
++O6kgpeZwGytCMbT0tLsBeBXQVTnR+oP27gELLZVqt0=
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-2.pk8 b/tests/tests/security/test-cert-2.pk8
new file mode 100644
index 0000000..5e73f27
--- /dev/null
+++ b/tests/tests/security/test-cert-2.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-2.x509.pem b/tests/tests/security/test-cert-2.x509.pem
new file mode 100644
index 0000000..f8e5e65
--- /dev/null
+++ b/tests/tests/security/test-cert-2.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbTCCAROgAwIBAgIJAIhVvR3SsrIlMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM
+B2VjLXAyNTYwHhcNMTgwNzEzMTc0MTUxWhcNMjgwNzEwMTc0MTUxWjAUMRIwEAYD
+VQQDDAllYy1wMjU2XzIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQdTMoEcq2X
+7jzs7w2pPWK0UMZ4gzOzbnVTzen3SrXfALu6a6lQ5oRh1wu8JxtiFR2tLeK/YgPN
+IHaAHHqdRCLho1AwTjAdBgNVHQ4EFgQUeZHZKwII/ESL9QbU78n/9CjLXl8wHwYD
+VR0jBBgwFoAU1BM1aLlbMBWLMiBx6oxD/1sFzMgwDAYDVR0TBAUwAwEB/zAKBggq
+hkjOPQQDAgNIADBFAiAnaauxtJ/C9TR5xK6SpmMdq/1SLJrLC7orQ+vrmcYwEQIh
+ANJg+x0fF2z5t/pgCYv9JDGfSQWj5f2hAKb+Giqxn/Ce
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-3.pk8 b/tests/tests/security/test-cert-3.pk8
new file mode 100644
index 0000000..d7309dd
--- /dev/null
+++ b/tests/tests/security/test-cert-3.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-3.x509.pem b/tests/tests/security/test-cert-3.x509.pem
new file mode 100644
index 0000000..c028ff7
--- /dev/null
+++ b/tests/tests/security/test-cert-3.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbjCCARWgAwIBAgIJAIOU9crRaomnMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMM
+CWVjLXAyNTZfMjAeFw0xODA3MTQwMDA1MjZaFw0yODA3MTEwMDA1MjZaMBQxEjAQ
+BgNVBAMMCWVjLXAyNTZfMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPMeYkMO
+nbb8WSjZdfxOR0GbrPyy4HyJKZ5s1+NE3SGt/TCNWMtJoaKj/srM7qSGIGnzC+Fk
+O8wlUEDYCJ37N0OjUDBOMB0GA1UdDgQWBBRvjQgosT769Xf8hrDpn6PlS8vP8DAf
+BgNVHSMEGDAWgBR5kdkrAgj8RIv1BtTvyf/0KMteXzAMBgNVHRMEBTADAQH/MAoG
+CCqGSM49BAMCA0cAMEQCICVr2qJ4TCc+TMKRpZWkZ3ne6d6QRNyferggMJVn35/p
+AiAaStjGmJG1qMR0NP6VQO0fSXm1+tNIPz+gTVZ3NVpXng==
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-4.pk8 b/tests/tests/security/test-cert-4.pk8
new file mode 100644
index 0000000..3675d50
--- /dev/null
+++ b/tests/tests/security/test-cert-4.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-4.x509.pem b/tests/tests/security/test-cert-4.x509.pem
new file mode 100644
index 0000000..4060400
--- /dev/null
+++ b/tests/tests/security/test-cert-4.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBezCCASCgAwIBAgIUbIy4qBhDPB5kMfsW+zrg+1rWCqcwCgYIKoZIzj0EAwIw
+FDESMBAGA1UEAwwJZWMtcDI1Nl8zMB4XDTIwMDUxMzE5MTUyOFoXDTMwMDUxMTE5
+MTUyOFowFDESMBAGA1UEAwwJZWMtcDI1Nl80MFkwEwYHKoZIzj0CAQYIKoZIzj0D
+AQcDQgAE20pgAx55rUnLdZAH1oVdRGm5HIurBlQ08vupca3n5NGVmaD2e15wjP2n
+VD5WMMN2nTfgk2QNfHaKFRRM0OXc9KNQME4wHQYDVR0OBBYEFG54lwMyVUM2tu6J
+JOqnAjDjk/Z4MB8GA1UdIwQYMBaAFG+NCCixPvr1d/yGsOmfo+VLy8/wMAwGA1Ud
+EwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAM54bnnsdUdEYILpyvkQYU/4B1j5
+gZ+w8UhpUGer4PzUAiEApIgeMy3ewhFq0rWc+JHQ8zH/fifne3xiBseYjZtTkzA=
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-with-1-2-4-in-rotation-history b/tests/tests/security/test-cert-with-1-2-4-in-rotation-history
new file mode 100644
index 0000000..7326e46
--- /dev/null
+++ b/tests/tests/security/test-cert-with-1-2-4-in-rotation-history
Binary files differ
diff --git a/tests/tests/security/testdata/permissionbackuptestapp/AndroidManifest.xml b/tests/tests/security/testdata/permissionbackuptestapp/AndroidManifest.xml
new file mode 100644
index 0000000..2b75d8c
--- /dev/null
+++ b/tests/tests/security/testdata/permissionbackuptestapp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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.security.permissionbackup" >
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+
+    <application
+        android:label="Android Permission Backup CTS App">
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/tests/settings/Android.bp b/tests/tests/settings/Android.bp
index b1a443e..1798e55 100644
--- a/tests/tests/settings/Android.bp
+++ b/tests/tests/settings/Android.bp
@@ -17,20 +17,13 @@
     static_libs: [
         "androidx.slice_slice-core",
         "androidx.slice_slice-view",
-        "androidx.test.core",
+        "androidx.window_window",
         "ctstestrunner-axt",
         "junit",
-        "kotlin-stdlib",
         "truth-prebuilt",
-        "ctsWindowExtLib",
     ],
 
     srcs: ["src/**/*.java"],
 
     sdk_version: "test_current",
 }
-
-android_library_import {
-    name: "ctsWindowExtLib",
-    aars: ["libs/cts_window_ext_lib.aar"],
-}
diff --git a/tests/tests/settings/AndroidManifest.xml b/tests/tests/settings/AndroidManifest.xml
index 007b36d..631d2b4 100644
--- a/tests/tests/settings/AndroidManifest.xml
+++ b/tests/tests/settings/AndroidManifest.xml
@@ -24,8 +24,10 @@
 
     <application>
         <uses-library android:name="android.test.runner"/>
-        <uses-library android:name="androidx.window.extensions" android:required="false"/>
-        <uses-library android:name="androidx.window.sidecar" android:required="false"/>
+
+        <property
+            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+            android:value="true" />
 
         <activity android:name=".RightPaneActivity"
                 android:exported="true">
diff --git a/tests/tests/settings/libs/cts_window_ext_lib.aar b/tests/tests/settings/libs/cts_window_ext_lib.aar
deleted file mode 100644
index ca58b36..0000000
--- a/tests/tests/settings/libs/cts_window_ext_lib.aar
+++ /dev/null
Binary files differ
diff --git a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
index 937141d..48b72d0 100644
--- a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
+++ b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
@@ -24,6 +24,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -55,10 +56,11 @@
 
     @Before
     public void setUp() throws Exception {
+        Context targetContext = InstrumentationRegistry.getInstrumentation()
+                .getTargetContext();
         boolean isFlagEnabled =
-                FeatureFlagUtils.isEnabled(InstrumentationRegistry.getInstrumentation()
-                        .getTargetContext(), "settings_support_large_screen");
-        boolean isSplitSupported = SplitController.getInstance().isSplitSupported();
+                FeatureFlagUtils.isEnabled(targetContext, "settings_support_large_screen");
+        boolean isSplitSupported = SplitController.getInstance(targetContext).isSplitSupported();
         mIsSplitSupported = isFlagEnabled && isSplitSupported;
         Log.d(TAG, "isFlagEnabled : " + isFlagEnabled);
         Log.d(TAG, "isSplitSupported : " + isSplitSupported);
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
index edc1cd2..812b1ef 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -421,7 +421,8 @@
             mLauncherContext1, mPackageContext1.getPackageName(), "s2", false));
     }
 
-    public void testSetDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+    // TODO: b/259468694
+    public void setDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
@@ -474,7 +475,8 @@
         });
     }
 
-    public void testRemoveAllDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+    // TODO: b/259468694
+    public void removeAllDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
@@ -533,7 +535,8 @@
         });
     }
 
-    public void testAddDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+    // TODO: b/259468694
+    public void addDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
@@ -588,7 +591,8 @@
         });
     }
 
-    public void testPushDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+    // TODO: b/259468694
+    public void pushDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
@@ -657,7 +661,8 @@
         SystemUtil.runShellCommand("cmd shortcut reset-config");
     }
 
-    public void testRemoveDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+    // TODO: b/259468694
+    public void removeDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
@@ -687,7 +692,8 @@
         });
     }
 
-    public void testRemoveLongLivedShortcuts_RemovesShortcutsFromDisk() throws Exception {
+    // TODO: b/259468694
+    public void removeLongLivedShortcuts_RemovesShortcutsFromDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
@@ -716,7 +722,8 @@
         });
     }
 
-    public void testDisableShortcuts_RemovesShortcutsFromDisk() throws Exception {
+    // TODO: b/259468694
+    public void disableShortcuts_RemovesShortcutsFromDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
@@ -746,7 +753,8 @@
         });
     }
 
-    public void testUpdateShortcuts_UpdateShortcutsOnDisk() throws Exception {
+    // TODO: b/259468694
+    public void updateShortcuts_UpdateShortcutsOnDisk() throws Exception {
         if (!isAppSearchEnabled()) {
             return;
         }
diff --git a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index be8c089..90e11d1 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -31,7 +31,6 @@
 import android.telecom.Call;
 import android.telecom.CallAudioState;
 import android.telecom.Connection;
-import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
@@ -301,23 +300,23 @@
             return;
         }
 
+        mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT);
+        TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
+        mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_2);
+        TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2);
+
         CountDownLatch latch = new CountDownLatch(1);
         mInCallCallbacks = new MockInCallService.InCallServiceCallbacks() {
             @Override
             public void onCallAdded(Call call, int numCalls) {
                 if (call.getState() == STATE_SELECT_PHONE_ACCOUNT) {
+                    call.phoneAccountSelected(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, true);
                     latch.countDown();
                 }
             }
         };
         MockInCallService.setCallbacks(mInCallCallbacks);
 
-        mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT);
-        TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
-        mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_2);
-        TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2);
-
-        PhoneAccountHandle cachedHandle = mTelecomManager.getUserSelectedOutgoingPhoneAccount();
         SystemUtil.runWithShellPermissionIdentity(() -> {
             mTelecomManager.setUserSelectedOutgoingPhoneAccount(null);
         });
@@ -327,10 +326,11 @@
             mTelecomManager.placeCall(testNumber, null);
 
             assertTrue(latch.await(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S, TimeUnit.SECONDS));
+            assertEquals(TestUtils.TEST_PHONE_ACCOUNT_HANDLE,
+                    mTelecomManager.getUserSelectedOutgoingPhoneAccount());
         } finally {
-            SystemUtil.runWithShellPermissionIdentity(() -> {
-                mTelecomManager.setUserSelectedOutgoingPhoneAccount(cachedHandle);
-            });
+            mTelecomManager.unregisterPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
+            mTelecomManager.unregisterPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2);
         }
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index ce92c8c..4029c50 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -33,6 +33,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -49,6 +50,7 @@
 import android.os.Looper;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -63,6 +65,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.CarrierPrivilegeUtils;
 import com.android.compatibility.common.util.PropertyUtil;
 import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -670,6 +673,7 @@
     }
 
     @Test
+    @ApiTest(apis = "android.telephony.SubscriptionManager#getSubscriptionsInGroup")
     public void testSubscriptionGroupingWithPermission() throws Exception {
         // Set subscription group with current sub Id.
         List<Integer> subGroup = new ArrayList();
@@ -680,8 +684,14 @@
         // Getting subscriptions in group.
         List<SubscriptionInfo> infoList = mSm.getSubscriptionsInGroup(uuid);
         assertNotNull(infoList);
+        assertTrue(infoList.isEmpty());
+
+        // has the READ_PRIVILEGED_PHONE_STATE permission
+        infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                (sm) -> sm.getSubscriptionsInGroup(uuid), READ_PRIVILEGED_PHONE_STATE);
+        assertNotNull(infoList);
         assertEquals(1, infoList.size());
-        assertNull(infoList.get(0).getGroupUuid());
+        assertEquals(uuid, infoList.get(0).getGroupUuid());
 
         infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getSubscriptionsInGroup(uuid));
@@ -698,33 +708,40 @@
         }
         availableInfoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getAvailableSubscriptionInfoList());
-        if (availableInfoList.size() > 1) {
-            List<Integer> availableSubGroup = availableInfoList.stream()
-                    .map(info -> info.getSubscriptionId())
-                    .filter(subId -> subId != mSubId)
-                    .collect(Collectors.toList());
+        // has the OPSTR_READ_DEVICE_IDENTIFIERS permission
+        try {
+            setIdentifierAccess(true);
+            if (availableInfoList.size() > 1) {
+                List<Integer> availableSubGroup = availableInfoList.stream()
+                        .map(info -> info.getSubscriptionId())
+                        .filter(subId -> subId != mSubId)
+                        .collect(Collectors.toList());
 
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
+                        (sm) -> sm.addSubscriptionsIntoGroup(availableSubGroup, uuid));
+
+                infoList = mSm.getSubscriptionsInGroup(uuid);
+                assertNotNull(infoList);
+                assertEquals(availableInfoList.size(), infoList.size());
+
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
+                        (sm) -> sm.removeSubscriptionsFromGroup(availableSubGroup, uuid));
+            }
+
+            // Remove from subscription group with current sub Id.
             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
-                    (sm) -> sm.addSubscriptionsIntoGroup(availableSubGroup, uuid));
+                    (sm) -> sm.removeSubscriptionsFromGroup(subGroup, uuid));
 
             infoList = mSm.getSubscriptionsInGroup(uuid);
             assertNotNull(infoList);
-            assertEquals(availableInfoList.size(), infoList.size());
-
-            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
-                    (sm) -> sm.removeSubscriptionsFromGroup(availableSubGroup, uuid));
+            assertTrue(infoList.isEmpty());
+        } finally {
+            setIdentifierAccess(false);
         }
-
-        // Remove from subscription group with current sub Id.
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
-                (sm) -> sm.removeSubscriptionsFromGroup(subGroup, uuid));
-
-        infoList = mSm.getSubscriptionsInGroup(uuid);
-        assertNotNull(infoList);
-        assertTrue(infoList.isEmpty());
     }
 
     @Test
+    @ApiTest(apis = "android.telephony.SubscriptionManager#getSubscriptionsInGroup")
     public void testAddSubscriptionIntoNewGroupWithPermission() throws Exception {
         // Set subscription group with current sub Id.
         List<Integer> subGroup = new ArrayList();
@@ -733,28 +750,33 @@
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
                 (sm) -> sm.addSubscriptionsIntoGroup(subGroup, uuid));
 
-        // Getting subscriptions in group.
         List<SubscriptionInfo> infoList = mSm.getSubscriptionsInGroup(uuid);
         assertNotNull(infoList);
-        assertEquals(1, infoList.size());
-        assertNull(infoList.get(0).getGroupUuid());
+        assertTrue(infoList.isEmpty());
 
-        infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
-                (sm) -> sm.getSubscriptionsInGroup(uuid));
-        assertNotNull(infoList);
-        assertEquals(1, infoList.size());
-        assertEquals(uuid, infoList.get(0).getGroupUuid());
+        // Getting subscriptions in group.
+        try {
+            setIdentifierAccess(true);
+            infoList = mSm.getSubscriptionsInGroup(uuid);
+            assertNotNull(infoList);
+            assertEquals(1, infoList.size());
+            assertEquals(uuid, infoList.get(0).getGroupUuid());
+        } finally {
+            setIdentifierAccess(false);
+        }
 
         // Remove from subscription group with current sub Id.
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
                 (sm) -> sm.removeSubscriptionsFromGroup(subGroup, uuid));
 
-        infoList = mSm.getSubscriptionsInGroup(uuid);
+        infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                (sm) -> sm.getSubscriptionsInGroup(uuid));
         assertNotNull(infoList);
         assertTrue(infoList.isEmpty());
     }
 
     @Test
+    @ApiTest(apis = "android.telephony.SubscriptionManager#setOpportunistic")
     public void testSettingOpportunisticSubscription() throws Exception {
         // Set subscription to be opportunistic. This should fail
         // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
@@ -762,6 +784,12 @@
             mSm.setOpportunistic(true, mSubId);
             fail();
         } catch (SecurityException expected) {
+            // Caller permission should not affect accessing SIMINFO table.
+            assertNotEquals(expected.getMessage(),
+                    "Access SIMINFO table from not phone/system UID");
+            // Caller does not have permission to manage mSubId.
+            assertEquals(expected.getMessage(),
+                    "Caller requires permission on sub " + mSubId);
         }
 
         // Shouldn't crash.
@@ -1459,4 +1487,13 @@
 
         return validCarrier && validNetworkType && validCapabilities;
     }
+
+    private void setIdentifierAccess(boolean allowed) {
+        String op = AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS;
+        AppOpsManager appOpsManager = InstrumentationRegistry.getContext().getSystemService(
+                AppOpsManager.class);
+        int mode = allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.opToDefaultMode(op);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                appOpsManager, (appOps) -> appOps.setUidMode(op, Process.myUid(), mode));
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index 291ce7c..8c39625 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -3567,6 +3567,7 @@
 
             ModemActivityInfo diff = activityInfo1.getDelta(activityInfo2);
             assertNotNull(diff);
+            assertTrue("two activityInfo are identical", !activityInfo1.equals(activityInfo2));
             assertTrue("diff is" + diff, diff.isValid() || diff.isEmpty());
         } finally {
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
index 3f6d02f..f969182 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
@@ -18,6 +18,8 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+
 import static junit.framework.Assert.assertNotNull;
 
 import static org.junit.Assert.assertEquals;
@@ -425,7 +427,7 @@
     /**
      * Setup the SMS filter with only the {@code clientPrefix}, and sends {@code text} to the
      * device. The SMS sent should not be written to the SMS provider. <p> If {@code expectVvmSms}
-     * is {@code true}, the SMS should be be caught by the SMS filter. The user should not receive
+     * is {@code true}, the SMS should be caught by the SMS filter. The user should not receive
      * the text, and the parsed result will be returned.* <p> If {@code expectVvmSms} is {@code
      * false}, the SMS should pass through the SMS filter. The user should receive the text, and
      * {@code null} be returned.
@@ -461,7 +463,7 @@
                     future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
                     throw new RuntimeException("Unexpected visual voicemail SMS received");
                 } catch (TimeoutException e) {
-                    // expected
+                    Log.i(TAG, "Expected TimeoutException" + e);
                     return null;
                 } catch (ExecutionException | InterruptedException e) {
                     throw new RuntimeException(e);
@@ -473,7 +475,6 @@
     @Nullable
     private VisualVoicemailSms getSmsFromData(VisualVoicemailSmsFilterSettings settings, short port,
             String text, boolean expectVvmSms) {
-
         mTelephonyManager.setVisualVoicemailSmsFilterSettings(settings);
 
         CompletableFuture<VisualVoicemailSms> future = new CompletableFuture<>();
@@ -497,7 +498,7 @@
                 future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
                 throw new RuntimeException("Unexpected visual voicemail SMS received");
             } catch (TimeoutException e) {
-                // expected
+                Log.i(TAG, "Expected TimeoutException!" + e);
                 return null;
             } catch (ExecutionException | InterruptedException e) {
                 throw new RuntimeException(e);
@@ -529,16 +530,28 @@
             StringBuilder messageBody = new StringBuilder();
             CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
             for (SmsMessage message : messages) {
-                if (message.getMessageBody() != null) {
-                    messageBody.append(message.getMessageBody());
-                } else if (message.getUserData() != null) {
+                String body = message.getMessageBody();
+
+                if ((body == null || (message.is3gpp()
+                        && message.getReceivedEncodingType() == ENCODING_8BIT))
+                        && message.getUserData() != null) {
+                    Log.d(TAG, "onReceive decode using UTF-8");
+                    // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using
+                    // 8BIT data coding scheme is our recommended way to send VVM SMS and is used in
+                    // CTS Tests. The OMTP visual voicemail specification does not specify the SMS
+                    // type and encoding.
                     ByteBuffer byteBuffer = ByteBuffer.wrap(message.getUserData());
                     try {
-                        messageBody.append(decoder.decode(byteBuffer).toString());
+                        body = decoder.decode(byteBuffer).toString();
                     } catch (CharacterCodingException e) {
+                        Log.e(TAG, "onReceive: got CharacterCodingException"
+                                + " when decoding with UTF-8, e = " + e);
                         return;
                     }
                 }
+                if (body != null) {
+                    messageBody.append(body);
+                }
             }
             if (!TextUtils.equals(mText, messageBody.toString())) {
                 return;
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
index dba15dc..87b08b9 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
@@ -22,16 +22,20 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
+
 import android.provider.Telephony;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiTest;
+
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -327,5 +331,55 @@
 
         DefaultSmsAppHelper.ensureDefaultSmsApp();
     }
-}
 
+    /**
+     * Verifies sql injection is not allowed within a URI.
+     */
+    @Test
+    @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+    public void query_msgParameter_sqlInjection() {
+        Uri uriWithSqlInjection = Uri.parse("content://mms-sms/pending?protocol=sms&message=1 "
+                + "union select type,name,tbl_name,rootpage,sql,1,1,1,1,1 FROM SQLITE_MASTER; --");
+        Cursor uriWithSqlInjectionCur = mContentResolver.query(uriWithSqlInjection, null,
+                null, null, null);
+        assertNull(uriWithSqlInjectionCur);
+    }
+
+    /**
+     * Verifies query() returns non-null cursor when valid URI is passed to it.
+     */
+    @Test
+    @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+    public void query_msgParameter_withoutSqlInjection() {
+        Uri uriWithoutSqlInjection = Uri.parse("content://mms-sms/pending?protocol=sms&message=1");
+        Cursor uriWithoutSqlInjectionCur = mContentResolver.query(uriWithoutSqlInjection,
+                null, null, null, null);
+        assertNotNull(uriWithoutSqlInjectionCur);
+    }
+
+    /**
+     * Verifies sql injection is not allowed within a URI.
+     */
+    @Test
+    @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+    public void query_threadIdParameter_sqlInjection() {
+        Uri uriWithSqlInjection = Uri.parse("content://mms-sms/conversations?simple=true&"
+                + "thread_type=1 union select type,name,tbl_name,rootpage,sql FROM SQLITE_MASTER;; --");
+        Cursor uriWithSqlInjectionCur = mContentResolver.query(uriWithSqlInjection,
+                new String[]{"1","2","3","4","5"}, null, null, null);
+        assertNull(uriWithSqlInjectionCur);
+    }
+
+    /**
+     * Verifies query() returns non-null cursor when valid URI is passed to it.
+     */
+    @Test
+    @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+    public void query_threadIdParameter_withoutSqlInjection() {
+        Uri uriWithoutSqlInjection = Uri.parse(
+                "content://mms-sms/conversations?simple=true&thread_type=1");
+        Cursor uriWithoutSqlInjectionCur = mContentResolver.query(uriWithoutSqlInjection,
+                new String[]{"1","2","3","4","5"}, null, null, null);
+        assertNotNull(uriWithoutSqlInjectionCur);
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
index 678ce90..9510d93 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
@@ -56,7 +56,9 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
 
+import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.ShellUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -204,6 +206,18 @@
         assertThat(mSimpleTextClassifier.getClassifyTextInvocationCount()).isEqualTo(1);
     }
 
+    // TODO: re-use now. Refactor to have a folder/test class for toolbar
+    @Test
+    @ApiTest(apis = "android.view.View#startActionMode")
+    public void smartSelection_toolbarContainerNoContentDescription() throws Exception {
+        smartSelectionInternal();
+
+        UiObject2 toolbarContainer =
+                sDevice.findObject(By.res("android", "floating_popup_container"));
+        assertThat(toolbarContainer).isNotNull();
+        assertThat(toolbarContainer.getContentDescription()).isNull();
+    }
+
     private void smartSelectionInternal() {
         ActivityScenario<TextViewActivity> scenario = rule.getScenario();
         AtomicInteger clickIndex = new AtomicInteger();
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index f00f0b1..5330b02 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -20,17 +20,20 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorSpace;
+import android.graphics.LinearGradient;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Picture;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
+import android.graphics.Shader;
 import android.graphics.drawable.NinePatchDrawable;
 import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapcomparers.BitmapComparer;
 import android.uirendering.cts.bitmapcomparers.ExactComparer;
 import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
 import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier;
 import android.uirendering.cts.bitmapverifiers.PerPixelBitmapVerifier;
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
@@ -40,9 +43,13 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ApiTest;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class ExactCanvasTests extends ActivityTestBase {
@@ -352,6 +359,138 @@
     }
 
     @Test
+    @ApiTest(apis = {"android.graphics.Canvas#drawVertices"})
+    public void testDrawVertices_doNotBlendColorsWithoutShader() {
+        BitmapVerifier verifier = new ColorVerifier(Color.BLUE);
+
+        createTest()
+                .addCanvasClient(
+                        (canvas, width, height) -> {
+                            float[] verts =
+                                    new float[] {
+                                        width, 0, width, height, 0, 0, 0, height,
+                                    };
+                            int[] vertColors =
+                                    new int[] {Color.BLUE, Color.BLUE, Color.BLUE, Color.BLUE};
+                            // Paint color should be ignored and not blend with vertex color.
+                            Paint paint = new Paint();
+                            paint.setColor(Color.RED);
+                            canvas.drawVertices(
+                                    Canvas.VertexMode.TRIANGLE_STRIP,
+                                    verts.length,
+                                    verts,
+                                    0,
+                                    null,
+                                    0,
+                                    vertColors,
+                                    0,
+                                    null,
+                                    0,
+                                    0,
+                                    paint);
+                        })
+                .runWithVerifier(verifier);
+    }
+
+    @Test
+    @ApiTest(apis = {"android.graphics.Canvas#drawVertices"})
+    public void testDrawVertices_blendVertexAndShaderColors() {
+        Color vertexColor = Color.valueOf(0.8f, 0.6f, 0.4f);
+        Color shaderColor = Color.valueOf(0.5f, 0.5f, 0.5f);
+        // drawVertices should blend vertex color and shader color with kModulate.
+        Color modulatedColor =
+                Color.valueOf(
+                        vertexColor.red() * shaderColor.red(),
+                        vertexColor.green() * shaderColor.green(),
+                        vertexColor.blue() * shaderColor.blue());
+        BitmapVerifier verifier = new ColorVerifier(modulatedColor.toArgb());
+
+        createTest()
+                .addCanvasClient(
+                        (canvas, width, height) -> {
+                            float[] verts =
+                                    new float[] {
+                                        width, 0, width, height, 0, 0, 0, height,
+                                    };
+                            int[] vertColors = new int[4];
+                            Arrays.fill(vertColors, vertexColor.toArgb());
+                            int[] shaderColors = new int[2];
+                            Arrays.fill(shaderColors, shaderColor.toArgb());
+
+                            Paint paint = new Paint();
+                            paint.setShader(
+                                    new LinearGradient(
+                                            0,
+                                            0,
+                                            width,
+                                            height,
+                                            shaderColors,
+                                            null,
+                                            Shader.TileMode.REPEAT));
+                            canvas.drawVertices(
+                                    Canvas.VertexMode.TRIANGLE_STRIP,
+                                    verts.length,
+                                    verts,
+                                    0,
+                                    verts,
+                                    0,
+                                    vertColors,
+                                    0,
+                                    null,
+                                    0,
+                                    0,
+                                    paint);
+                        })
+                .runWithVerifier(verifier);
+    }
+
+    @Test
+    @ApiTest(apis = {"android.graphics.Canvas#drawVertices"})
+    public void testDrawVertices_ignoreShaderIfTexsNotSet() {
+        Color vertexColor = Color.valueOf(0.8f, 0.6f, 0.4f);
+        Color shaderColor = Color.valueOf(0.5f, 0.5f, 0.5f); // Should be ignored.
+        BitmapVerifier verifier = new ColorVerifier(vertexColor.toArgb());
+
+        createTest()
+                .addCanvasClient(
+                        (canvas, width, height) -> {
+                            float[] verts =
+                                    new float[] {
+                                        width, 0, width, height, 0, 0, 0, height,
+                                    };
+                            int[] vertColors = new int[4];
+                            Arrays.fill(vertColors, vertexColor.toArgb());
+                            int[] shaderColors = new int[2];
+                            Arrays.fill(shaderColors, shaderColor.toArgb());
+
+                            Paint paint = new Paint();
+                            paint.setShader(
+                                    new LinearGradient(
+                                            0,
+                                            0,
+                                            width,
+                                            height,
+                                            shaderColors,
+                                            null,
+                                            Shader.TileMode.REPEAT));
+                            canvas.drawVertices(
+                                    Canvas.VertexMode.TRIANGLE_STRIP,
+                                    verts.length,
+                                    verts,
+                                    0,
+                                    null,
+                                    0,
+                                    vertColors,
+                                    0,
+                                    null,
+                                    0,
+                                    0,
+                                    paint);
+                        })
+                .runWithVerifier(verifier);
+    }
+
+    @Test
     public void testColorLongs() {
         createTest()
                 .addCanvasClient((canvas, width, height) -> {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
index 3148dcc..0674aff 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
@@ -34,6 +34,9 @@
 import android.uirendering.cts.testinfrastructure.CanvasClient
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import com.android.compatibility.common.util.ApiTest
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
 import org.junit.Assert
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -373,6 +376,37 @@
     }
 
     @Test
+    @ApiTest(apis = arrayOf("android.graphics.Shader#setLocalMatrix"))
+    fun testComposeShaderLocalMatrix() {
+        val shaderA = RuntimeShader(simpleRedShader)
+
+        // Create a runtime shader that calls a decal image shader with translation local matrix.
+        val bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888, true)
+        bitmap.eraseColor(Color.GREEN)
+        val bitmapShader = BitmapShader(bitmap, Shader.TileMode.DECAL, Shader.TileMode.DECAL)
+        val matrix = Matrix()
+        matrix.setTranslate(100f, 0f)
+        bitmapShader.setLocalMatrix(matrix)
+        val shaderB = RuntimeShader(samplingShader)
+        shaderB.setInputShader("inputShader", bitmapShader)
+
+        // This compose shader will have a local matrix that compensates for the image shader's
+        // translation.
+        val composeShader = ComposeShader(shaderA, shaderB, BlendMode.SRC_OVER)
+        matrix.setTranslate(-100f, 0f)
+        composeShader.setLocalMatrix(matrix)
+
+        val paint = Paint()
+        paint.shader = composeShader
+
+        val rect = Rect(0, 0, 20, 20)
+
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.GREEN, rect, 0))
+    }
+
+    @Test
     fun testLinearColorIntrinsic() {
         val colorA = Color.valueOf(0.75f, 0.25f, 0.0f, 1.0f)
         val colorB = Color.valueOf(0.0f, 0.75f, 0.25f, 1.0f)
diff --git a/tests/tests/voiceinteraction/Android.bp b/tests/tests/voiceinteraction/Android.bp
index b847f7f..e534c35 100644
--- a/tests/tests/voiceinteraction/Android.bp
+++ b/tests/tests/voiceinteraction/Android.bp
@@ -20,6 +20,7 @@
     name: "CtsVoiceInteractionTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
+        "CtsAttentionServiceDevice",
         "CtsVoiceInteractionCommon",
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
@@ -35,7 +36,7 @@
         "service/src/android/voiceinteraction/service/MainInteractionSessionService.java",
         "service/src/android/voiceinteraction/service/MainRecognitionService.java",
         "service/src/android/voiceinteraction/service/EventPayloadParcelable.java",
-        "service/src/android/voiceinteraction/service/EventPayloadParcelable.aidl"
+        "service/src/android/voiceinteraction/service/EventPayloadParcelable.aidl",
     ],
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/tests/voiceinteraction/AndroidManifest.xml b/tests/tests/voiceinteraction/AndroidManifest.xml
index 1b8b513..6401843 100644
--- a/tests/tests/voiceinteraction/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/AndroidManifest.xml
@@ -96,6 +96,14 @@
             <meta-data android:name="android.speech"
                      android:resource="@xml/recognition_service" />
         </service>
+        <service android:name="android.attentionservice.cts.CtsTestAttentionService"
+                 android:label="CtsTestAttentionService"
+                 android:permission="android.permission.BIND_ATTENTION_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.attention.AttentionService"/>
+            </intent-filter>
+        </service>
       <receiver android:name="VoiceInteractionTestReceiver"
            android:exported="true"/>
     </application>
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index 5c1e926..4a7b5cc 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -17,6 +17,9 @@
 package android.voiceinteraction.cts;
 
 import static android.content.pm.PackageManager.FEATURE_MICROPHONE;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.perform;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.performAndGetDetectionResult;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.testHotwordDetection;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -24,7 +27,6 @@
 
 import android.app.Instrumentation;
 import android.app.compat.CompatChanges;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
@@ -44,7 +46,6 @@
 import android.voiceinteraction.service.EventPayloadParcelable;
 import android.voiceinteraction.service.MainHotwordDetectionService;
 
-import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.RequiresDevice;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -139,9 +140,12 @@
     @Test
     public void testHotwordDetectionService_validHotwordDetectionComponentName_triggerSuccess()
             throws Throwable {
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
     }
 
     @Test
@@ -157,22 +161,31 @@
         receiver.register();
 
         // Create SoftwareHotwordDetector
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         // Destroy detector
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
                 Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         // Create AlwaysOnHotwordDetector
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         verifyDetectedResult(
-                performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST),
+                performAndGetDetectionResult(
+                        mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
                 MainHotwordDetectionService.DETECTED_RESULT);
         verifyMicrophoneChip(true);
     }
@@ -180,17 +193,21 @@
     @Test
     public void testVoiceInteractionService_withoutManageHotwordDetectionPermission_triggerFailure()
             throws Throwable {
-        testHotwordDetection(Utils.VIS_WITHOUT_MANAGE_HOTWORD_DETECTION_PERMISSION_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.VIS_WITHOUT_MANAGE_HOTWORD_DETECTION_PERMISSION_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
     }
 
     @Test
     public void testVoiceInteractionService_holdBindHotwordDetectionPermission_triggerFailure()
             throws Throwable {
-        testHotwordDetection(Utils.VIS_HOLD_BIND_HOTWORD_DETECTION_PERMISSION_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.VIS_HOLD_BIND_HOTWORD_DETECTION_PERMISSION_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
     }
 
     @Test
@@ -199,12 +216,17 @@
             throws Throwable {
         Thread.sleep(CLEAR_CHIP_MS);
         // Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         verifyDetectedResult(
-                performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST),
+                performAndGetDetectionResult(
+                        mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
                 MainHotwordDetectionService.DETECTED_RESULT);
         verifyMicrophoneChip(true);
     }
@@ -215,11 +237,16 @@
             throws Throwable {
         Thread.sleep(CLEAR_CHIP_MS);
         // Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
-        assertThat(performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONREJECT_TEST))
+        assertThat(performAndGetDetectionResult(
+                mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_DSP_ONREJECT_TEST,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC))
                 .isEqualTo(MainHotwordDetectionService.REJECTED_RESULT);
         verifyMicrophoneChip(false);
     }
@@ -229,13 +256,17 @@
             throws Throwable {
         Thread.sleep(CLEAR_CHIP_MS);
         // Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         verifyDetectedResult(
                 performAndGetDetectionResult(
-                        Utils.HOTWORD_DETECTION_SERVICE_EXTERNAL_SOURCE_ONDETECT_TEST),
+                        mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_EXTERNAL_SOURCE_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
                 MainHotwordDetectionService.DETECTED_RESULT);
         verifyMicrophoneChip(true);
     }
@@ -246,12 +277,17 @@
             throws Throwable {
         Thread.sleep(CLEAR_CHIP_MS);
         // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         verifyDetectedResult(
-                performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST),
+                performAndGetDetectionResult(
+                        mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
                 MainHotwordDetectionService.DETECTED_RESULT);
         verifyMicrophoneChip(true);
     }
@@ -261,17 +297,23 @@
     public void testHotwordDetectionService_onStopDetection()
             throws Throwable {
         // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         // The HotwordDetectionService can't report any result after recognition is stopped. So
         // restart it after stopping; then the service can report a special result.
-        perform(Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST);
-        perform(Utils.HOTWORD_DETECTION_SERVICE_CALL_STOP_RECOGNITION);
+        perform(mActivityTestRule, Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+        perform(mActivityTestRule, Utils.HOTWORD_DETECTION_SERVICE_CALL_STOP_RECOGNITION,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
         EventPayloadParcelable result =
                 (EventPayloadParcelable) performAndGetDetectionResult(
-                        Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST);
+                        mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         verifyDetectedResult(
                 result, MainHotwordDetectionService.DETECTED_RESULT_AFTER_STOP_DETECTION);
@@ -281,9 +323,11 @@
     @RequiresDevice
     public void testHotwordDetectionService_concurrentCapture() throws Throwable {
         // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         SystemUtil.runWithShellPermissionIdentity(() -> {
             AudioRecord record =
@@ -305,7 +349,9 @@
                 record.startRecording();
                 verifyDetectedResult(
                         performAndGetDetectionResult(
-                                Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST),
+                                mActivityTestRule, mContext,
+                                Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+                                Utils.HOTWORD_DETECTION_SERVICE_BASIC),
                         MainHotwordDetectionService.DETECTED_RESULT);
                 // TODO: Test that it still works after restarting the process or killing audio
                 //  server.
@@ -319,14 +365,18 @@
     public void testHotwordDetectionService_processDied_triggerOnError()
             throws Throwable {
         // Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         // Use AlwaysOnHotwordDetector to test process died of HotwordDetectionService
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_PROCESS_DIED_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_PROCESS_DIED_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_GET_ERROR);
+                Utils.HOTWORD_DETECTION_SERVICE_GET_ERROR,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         // ActivityManager will schedule a timer to restart the HotwordDetectionService due to
         // we crash the service in this test case. It may impact the other test cases when
@@ -339,68 +389,47 @@
     @Test
     public void testHotwordDetectionService_destroyDspDetector_activeDetectorRemoved() {
         // Create AlwaysOnHotwordDetector
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         // Can no longer use the detector because it is in an invalid state
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
     }
 
     @Test
     public void testHotwordDetectionService_destroySoftwareDetector_activeDetectorRemoved() {
         // Create SoftwareHotwordDetector
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
                 Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
 
         // Can no longer use the detector because it is in an invalid state
-        testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
-    }
-
-    private void testHotwordDetection(int testType, String expectedIntent, int expectedResult) {
-        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
-                expectedIntent);
-        receiver.register();
-        perform(testType);
-        final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
-        receiver.unregisterQuietly();
-
-        assertThat(intent).isNotNull();
-        assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(expectedResult);
-    }
-
-    @NonNull
-    private Parcelable performAndGetDetectionResult(int testType) {
-        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
-                Utils.HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT_INTENT);
-        receiver.register();
-        perform(testType);
-        final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
-        receiver.unregisterQuietly();
-
-        assertThat(intent).isNotNull();
-        final Parcelable result = intent.getParcelableExtra(Utils.KEY_TEST_RESULT);
-        assertThat(result).isNotNull();
-        return result;
-    }
-
-    private void perform(int testType) {
-        mActivityTestRule.getScenario().onActivity(
-                activity -> activity.triggerHotwordDetectionServiceTest(
-                        Utils.HOTWORD_DETECTION_SERVICE_BASIC, testType));
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
     }
 
     // TODO: Implement HotwordDetectedResult#equals to override the Bundle equality check; then
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceProximityTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceProximityTest.java
new file mode 100644
index 0000000..a47fb2b
--- /dev/null
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceProximityTest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voiceinteraction.cts;
+
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.performAndGetDetectionResult;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.testHotwordDetection;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.attentionservice.cts.CtsTestAttentionService;
+import android.os.Parcelable;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.service.voice.HotwordDetectedResult;
+import android.voiceinteraction.common.Utils;
+import android.voiceinteraction.service.EventPayloadParcelable;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.RequiredServiceRule;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for using the Attention Service inside VoiceInteractionService using
+ * a basic HotwordDetectionService.
+ */
+@ApiTest(apis = {"android.service.voice.HotwordDetectedResult#getExtras"})
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "No real use case for instant mode hotword detection service")
+public final class HotwordDetectionServiceProximityTest
+        extends AbstractVoiceInteractionBasicTestCase {
+    private static final String ATTENTION_SERVICE = "attention";
+
+    @ClassRule
+    public static final RequiredServiceRule ATTENTION_SERVICE_RULE =
+            new RequiredServiceRule(ATTENTION_SERVICE);
+
+    @Rule
+    public final DeviceConfigStateChangerRule mEnableAttentionManagerServiceRule =
+            new DeviceConfigStateChangerRule(sInstrumentation.getTargetContext(),
+                    DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                    SERVICE_ENABLED,
+                    "true");
+
+    private static final String EXTRA_PROXIMITY =
+            "android.service.voice.extra.PROXIMITY";
+
+    private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+    private static final String SERVICE_ENABLED = "service_enabled";
+    private static final String FAKE_SERVICE_PACKAGE =
+            HotwordDetectionServiceProximityTest.class.getPackage().getName();
+    private static final double PROXIMITY_NEAR_METERS = 2.0;
+    private static final double PROXIMITY_FAR_METERS = 6.0;
+    private static final int PROXIMITY_UNKNOWN = -1;
+    private static final int PROXIMITY_NEAR = 1;
+    private static final int PROXIMITY_FAR = 2;
+    private static final boolean ENABLE_PROXIMITY_RESULT = true;
+
+    @BeforeClass
+    public static void enableAttentionService() throws InterruptedException {
+        CtsTestAttentionService.reset();
+        assertThat(setTestableAttentionService(FAKE_SERVICE_PACKAGE)).isTrue();
+        assertThat(getAttentionServiceComponent()).contains(FAKE_SERVICE_PACKAGE);
+        runShellCommand("cmd attention call checkAttention");
+    }
+
+    @AfterClass
+    public static void clearAttentionService() {
+        runShellCommand("cmd attention clearTestableAttentionService");
+    }
+
+    @Test
+    @RequiresDevice
+    public void testAttentionService_onDetectFromDsp() {
+        // Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+        // by default, proximity should not be returned.
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                null);
+
+        // when proximity is unknown, proximity should not be returned.
+        CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN);
+
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                null);
+
+        CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS);
+
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                PROXIMITY_NEAR);
+
+        CtsTestAttentionService.respondProximity(PROXIMITY_FAR_METERS);
+
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                PROXIMITY_FAR);
+
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+    }
+
+    @Test
+    @RequiresDevice
+    public void testAttentionService_onDetectFromMic_noUpdates() {
+        // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                null);
+
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+    }
+
+    @Test
+    @RequiresDevice
+    public void testAttentionService_onDetectFromMic_unknownProximity() {
+        // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+        CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN);
+
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                null);
+
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+    }
+
+    @Test
+    @RequiresDevice
+    public void testAttentionService_onDetectFromMic_updatedProximity() {
+        // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+        CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS);
+
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                PROXIMITY_NEAR);
+
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+    }
+
+    @Test
+    @RequiresDevice
+    public void testAttentionService_onDetectFromExternalSource_doesNotReceiveProximity() {
+        // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+        CtsTestAttentionService.respondProximity(PROXIMITY_FAR);
+
+        verifyProximityBundle(
+                performAndGetDetectionResult(mActivityTestRule, mContext,
+                        Utils.HOTWORD_DETECTION_SERVICE_EXTERNAL_SOURCE_ONDETECT_TEST,
+                        Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+                null);
+
+        testHotwordDetection(mActivityTestRule, mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+                Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+    }
+
+    // simply check that the proximity values are equal.
+    private void verifyProximityBundle(Parcelable result, Integer expected) {
+        assertThat(result).isInstanceOf(EventPayloadParcelable.class);
+        HotwordDetectedResult hotwordDetectedResult =
+                ((EventPayloadParcelable) result).mHotwordDetectedResult;
+        assertThat(hotwordDetectedResult).isNotNull();
+        if (expected == null || !ENABLE_PROXIMITY_RESULT) {
+            assertThat(hotwordDetectedResult.getExtras().containsKey(EXTRA_PROXIMITY)).isFalse();
+        } else {
+            assertThat(hotwordDetectedResult.getExtras().containsKey(EXTRA_PROXIMITY)).isTrue();
+            assertThat(hotwordDetectedResult.getExtras().getInt(EXTRA_PROXIMITY))
+                    .isEqualTo(expected);
+        }
+    }
+
+    private static String getAttentionServiceComponent() {
+        return runShellCommand("cmd attention getAttentionServiceComponent");
+    }
+
+    private static boolean setTestableAttentionService(String service) {
+        return runShellCommand("cmd attention setTestableAttentionService " + service)
+                .equals("true");
+    }
+
+    @Override
+    public String getVoiceInteractionService() {
+        return "android.voiceinteraction.cts/"
+                + "android.voiceinteraction.service.BasicVoiceInteractionService";
+    }
+}
+
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
index 70200b8..2792389 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
@@ -21,13 +21,11 @@
 import android.content.Intent;
 import android.util.Log;
 import android.voiceinteraction.common.Utils;
-import android.voiceinteraction.service.BasicVoiceInteractionService;
-import android.voiceinteraction.service.MainInteractionService;
 
 public class TestVoiceInteractionServiceActivity extends Activity {
     static final String TAG = "TestVoiceInteractionServiceActivity";
 
-    void triggerHotwordDetectionServiceTest(int serviceType, int testEvent) {
+    public void triggerHotwordDetectionServiceTest(int serviceType, int testEvent) {
         Intent serviceIntent = new Intent();
         if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_NONE) {
             serviceIntent.setComponent(new ComponentName(this,
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/testcore/VoiceInteractionDetectionHelper.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/testcore/VoiceInteractionDetectionHelper.java
new file mode 100644
index 0000000..9e7f573
--- /dev/null
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/testcore/VoiceInteractionDetectionHelper.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voiceinteraction.cts.testcore;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.voiceinteraction.common.Utils;
+import android.voiceinteraction.cts.TestVoiceInteractionServiceActivity;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+/**
+ * A helper class to perform an intent to start the Voice Interaction Service and receive Hotword
+ * Detection Result.
+ */
+public class VoiceInteractionDetectionHelper {
+    private static final int TEST_RESULT_AWAIT_TIMEOUT_MS = 10 * 1000;
+
+    public static void perform(
+            ActivityScenarioRule<TestVoiceInteractionServiceActivity> activityTestRule,
+            int testType, int serviceType) {
+        activityTestRule.getScenario().onActivity(
+                activity -> activity.triggerHotwordDetectionServiceTest(
+                        serviceType, testType));
+    }
+
+    public static void testHotwordDetection(
+            ActivityScenarioRule<TestVoiceInteractionServiceActivity> activityTestRule,
+            Context context, int testType, String expectedIntent, int expectedResult,
+            int serviceType) {
+        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context,
+                expectedIntent);
+        receiver.register();
+        perform(activityTestRule, testType, serviceType);
+        final Intent intent = receiver.awaitForBroadcast(TEST_RESULT_AWAIT_TIMEOUT_MS);
+        receiver.unregisterQuietly();
+
+        assertThat(intent).isNotNull();
+        assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(expectedResult);
+    }
+
+    @NonNull
+    public static Parcelable performAndGetDetectionResult(
+            ActivityScenarioRule<TestVoiceInteractionServiceActivity> activityTestRule,
+            Context context, int testType, int serviceType) {
+        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context,
+                Utils.HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT_INTENT);
+        receiver.register();
+        perform(activityTestRule, testType, serviceType);
+        final Intent intent = receiver.awaitForBroadcast(TEST_RESULT_AWAIT_TIMEOUT_MS);
+        receiver.unregisterQuietly();
+
+        assertThat(intent).isNotNull();
+        final Parcelable result = intent.getParcelableExtra(Utils.KEY_TEST_RESULT);
+        assertThat(result).isNotNull();
+        return result;
+    }
+}
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index 0d562db..5f41171 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -42,6 +42,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.cts.EditTextCursorCtsActivity"
+                  android:label="Empty test activity"
+                  android:launchMode="singleTop"
+                  android:exported="true">
+            <intent-filter>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.widget.cts.AbsoluteLayoutCtsActivity"
              android:label="AbsoluteLayoutCtsActivity"
              android:exported="true">
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextCursorCtsActivity.java b/tests/tests/widget/src/android/widget/cts/EditTextCursorCtsActivity.java
new file mode 100644
index 0000000..a393c58
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/EditTextCursorCtsActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.EditText;
+
+
+public class EditTextCursorCtsActivity extends Activity {
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.edittext_layout);
+
+        EditText et = findViewById(R.id.edittext_simple1);
+        et.setText("test for blinking cursor");
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 2370562..ca3d7db 100755
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -19,6 +19,7 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -27,12 +28,14 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.os.SystemClock;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
 import android.text.Editable;
 import android.text.InputFilter;
 import android.text.InputType;
@@ -51,6 +54,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.inputmethod.EditorInfo;
 import android.widget.EditText;
+import android.widget.Editor;
 import android.widget.TextView;
 import android.widget.TextView.BufferType;
 
@@ -89,6 +93,8 @@
     @Rule
     public ActivityTestRule<EditTextCtsActivity> mActivityRule =
             new ActivityTestRule<>(EditTextCtsActivity.class);
+    public ActivityTestRule<EditTextCursorCtsActivity> mEmptyActivityRule =
+            new ActivityTestRule<>(EditTextCursorCtsActivity.class, false, false);
 
     @Before
     public void setup() {
@@ -760,4 +766,67 @@
         object.click();
         SystemClock.sleep(ViewConfiguration.getDoubleTapTimeout() + 50);
     }
+
+    @Test
+    public void testCursorNotBlinkingOnNewActivity_WithoutFocus() {
+        Activity testActivity = mEmptyActivityRule.launchActivity(null);
+        EditText et = testActivity.findViewById(R.id.edittext_simple1);
+        Editor editor = et.getEditorForTesting();
+        boolean cursorBlinking = editor.isBlinking();
+        assertFalse(cursorBlinking);
+    }
+
+    @Test
+    public void testCursorBlinkingOnNewActivity_WithFocus() {
+        Activity testActivity = mEmptyActivityRule.launchActivity(null);
+        EditText et = testActivity.findViewById(R.id.edittext_simple1);
+        Editor editor = et.getEditorForTesting();
+
+        mInstrumentation.runOnMainSync(() -> {
+            et.requestFocus();
+        });
+
+        boolean cursorBlinking = editor.isBlinking();
+        assertTrue(cursorBlinking);
+    }
+
+    @Test
+    public void testSuspendAndResumeBlinkingCursor() {
+        Activity testActivity = mEmptyActivityRule.launchActivity(null);
+        final EditText et = testActivity.findViewById(R.id.edittext_simple1);
+        Editor editor = et.getEditorForTesting();
+
+        mInstrumentation.runOnMainSync(() -> {
+            et.requestFocus();
+        });
+
+        UiDevice device =  UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        boolean cursorBlinking = editor.isBlinking();
+        assertTrue(cursorBlinking);
+
+        // Send activity to the background.
+        device.pressHome();
+        device.waitForIdle();
+
+        cursorBlinking = editor.isBlinking();
+        assertFalse(cursorBlinking);
+
+        // Bring the activity back into the foreground
+        Intent resumeActivity = new Intent(mInstrumentation.getContext(),
+                EditTextCursorCtsActivity.class);
+        resumeActivity.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        mActivity.startActivity(resumeActivity);
+
+        // Check if the activity is in the foreground.
+        device.wait(Until.findObject(By.text("test for blinking cursor")), 2000);
+
+        mInstrumentation.runOnMainSync(() -> {
+            et.requestFocus();
+        });
+
+        cursorBlinking = editor.isBlinking();
+        assertTrue(cursorBlinking);
+    }
+
 }
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index ac9e140..f421d31 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -628,6 +628,8 @@
 
         mRemoteViews.setBitmap(R.id.remoteView_absolute, "setImageBitmap", bitmap);
         assertThrowsOnReapply(ActionException.class);
+
+        assertEquals(bitmap.getAllocationByteCount(), mRemoteViews.estimateMemoryUsage());
     }
 
     @Test
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
index c0e5624..bbd4378 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -292,7 +292,7 @@
             state = waitForNextNetworkState();
         }
         if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                && state == NetworkInfo.DetailedState.CONNECTING) {
+            && state == NetworkInfo.DetailedState.CONNECTING) {
             state = waitForNextNetworkState();
         }
         return state == NetworkInfo.DetailedState.CONNECTED;
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 1937288..4632d03 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -80,6 +80,7 @@
 import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.RequiredServiceRule;
@@ -442,6 +443,38 @@
         Mockito.verify(mockCallback, Mockito.times(1)).onHideTranslation(any());
     }
 
+
+    @Test
+    @ApiTest(apis = {"android.view.translation.UiTranslationManagerTest#startUiTranslation",
+            "android.view.View#setViewTranslationCallback"})
+    public void testUiTranslation_CustomViewTranslationCallback_DetachAndReattach()
+            throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        // Set ViewTranslationCallback
+        ViewTranslationCallback mockCallback = Mockito.mock(ViewTranslationCallback.class);
+        mTextView.setViewTranslationCallback(mockCallback);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        mActivityScenario.onActivity(activity -> {
+            // Toggling the visibility will cause detach and reattach. This test ensures the
+            // custom callback is not cleared when this happens.
+            mTextView.setVisibility(View.INVISIBLE);
+            mTextView.setVisibility(View.VISIBLE);
+        });
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        ArgumentCaptor<View> viewArgumentCaptor = ArgumentCaptor.forClass(View.class);
+        Mockito.verify(mockCallback, Mockito.times(1)).onShowTranslation(
+                viewArgumentCaptor.capture());
+        TextView capturedView = (TextView) viewArgumentCaptor.getValue();
+        assertThat(capturedView.getAutofillId()).isEqualTo(mTextView.getAutofillId());
+    }
+
     @Test
     @FlakyTest(bugId = 192418800)
     public void testUiTranslation_ViewTranslationCallback_paddingText() throws Throwable {
@@ -667,7 +700,50 @@
         } finally {
             manager.unregisterUiTranslationStateCallback(mockCallback);
         }
-        // TODO(b/191417938): add a test to verify startUiTranslation + Activity destroyed.
+    }
+
+    @Test
+    public void testActivityDestroyedWithoutFinishTranslation() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        try {
+            startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+            Mockito.verify(mockCallback, Mockito.times(1))
+                    .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+            pauseUiTranslation(contentCaptureContext);
+
+            Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+            resumeUiTranslation(contentCaptureContext);
+
+            Mockito.verify(mockCallback, Mockito.times(1))
+                    .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+
+            // Make sure onFinished will still be called if Activity is destroyed, even without a
+            // finishTranslation call.
+            mActivityScenario.moveToState(Lifecycle.State.DESTROYED);
+            mActivityScenario = null;
+            SystemClock.sleep(UI_WAIT_TIMEOUT);
+            Mockito.verify(mockCallback, Mockito.times(1)).onFinished(any(String.class));
+        } finally {
+            manager.unregisterUiTranslationStateCallback(mockCallback);
+        }
     }
 
     @Test
@@ -868,6 +944,45 @@
     }
 
     @Test
+    public void testCallbackRegisteredAfterActivityDestroyedWithoutFinishTranslation()
+            throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        pauseUiTranslation(contentCaptureContext);
+
+        resumeUiTranslation(contentCaptureContext);
+
+        // Note: No finishTranslation call; Activity is destroyed.
+        mActivityScenario.moveToState(Lifecycle.State.DESTROYED);
+        mActivityScenario = null;
+        SystemClock.sleep(UI_WAIT_TIMEOUT);
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        try {
+            // Callback should receive no events.
+            SystemClock.sleep(UI_WAIT_TIMEOUT);
+            Mockito.verifyZeroInteractions(mockCallback);
+        } finally {
+            manager.unregisterUiTranslationStateCallback(mockCallback);
+        }
+    }
+
+    @Test
     public void testCallbackCalledOnceAfterDuplicateCalls() throws Throwable {
         final Pair<List<AutofillId>, ContentCaptureContext> result =
                 enableServicesAndStartActivityForTranslation();
diff --git a/tools/cts-tradefed/res/config/cts-slim-stable.xml b/tools/cts-tradefed/res/config/cts-slim-stable.xml
new file mode 100644
index 0000000..d36a7b6
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts-slim-stable.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration
+    description="Runs stable set of CTS tests for virtual device slim image">
+
+  <include name="cts-virtual-device-stable"/>
+
+  <option name="plan" value="cts-slim-stable"/>
+
+  <!-- These core test suites are inexplicably not included in cts-virtual-device-stable, so add them here -->
+  <option name="compatibility:include-filter" value="CtsAppTestCases"/>
+  <option name="compatibility:include-filter" value="CtsContentTestCases"/>
+  <option name="compatibility:include-filter" value="CtsDisplayTestCases"/>
+  <option name="compatibility:include-filter" value="CtsGraphicsTestCases"/>
+  <option name="compatibility:include-filter" value="CtsOsTestCases"/>
+  <option name="compatibility:include-filter" value="CtsUtilTestCases"/>
+  <option name="compatibility:include-filter" value="CtsViewTestCases"/>
+  <option name="compatibility:include-filter" value="CtsWidgetTestCases"/>
+
+  <!-- flaky tests -->
+  <option name="compatibility:exclude-filter"
+          value="CtsAppTestCases android.app.cts.ActivityManagerFgsBgStartTest#testOverlappedTempAllowList"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testBackgroundCheckActivityService"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testFgsSticky1"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testCantSaveStateLaunchAndSwitch" />
+  <option name="compatibility:exclude-filter"
+          value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testFgsSticky1"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsAppTestCases android.app.cts.AlertDialog_BuilderTest"/>
+  <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationUriPermissionsGranted"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsContentTestCases android.content.cts.ContentProviderClientTest#testBulkInsertTimeout"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsContentTestCases android.content.cts.ContentProviderClientTest#testUncanonicalizeTimeout" />
+  <option name="compatibility:exclude-filter"
+      value="CtsContentTestCases android.content.cts.ContentResolverSyncTestCase#testCallMultipleAccounts" />
+  <option name="compatibility:exclude-filter"
+      value="CtsContentTestCases android.content.cts.ContentQueryMapTest#testSetKeepUpdated" />
+  <option name="compatibility:exclude-filter"
+          value="CtsGraphicsTestCases android.graphics.cts.AnimatorLeakTest#testPauseResume"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsLocationFineTestCases android.location.cts.fine.LocationManagerFineTest#testRegisterGnssMeasurementsCallback" />
+  <option name="compatibility:exclude-filter" value="CtsTransitionTestCases"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsVoiceInteractionTestCases android.voiceinteraction.cts.DirectActionsTest" />
+  <option name="compatibility:exclude-filter"
+      value="CtsWidgetTestCases android.widget.cts.ZoomControlsTest#testHasFocus" />
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.TextViewTest#testOnBackInvokedCallback"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.TextViewTest#testUndo_imeInsertAndDeleteLatin"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.TimePickerTest#testConstructorNullContext2"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ToggleButtonTest#testAttributesFromLayout"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ToggleButtonTest#testSetChecked"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testNavigationConfiguration"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testMenuOverflowShowHide"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testTitleAndSubtitleContent"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testCurrentContentInsetsRtl"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testMenuContent"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.TwoLineListItemTest#testConstructorWithNullContext2"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.TwoLineListItemTest#testConstructor"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ViewAnimatorTest#testAccessDisplayedChild"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ViewAnimatorTest#testGetBaseline"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ZoomButtonTest#testConstructorWithNullContext2"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ZoomControlsTest#testSetZoomSpeed"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.inline.InlineContentViewTest#testSetSurfaceControlCallback"/>
+<option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ViewFlipperTest#testConstructorNullContext"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsWidgetTestCases android.widget.cts.ViewFlipperTest#testConstructor"/>
+
+  <!-- also fails on sdk_phone -->
+  <!-- causes bluetooth crash b/254094190 -->
+  <option name="compatibility:exclude-filter"
+          value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testFgsSticky3"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.SystemFeaturesTest#testLocationFeatures" />
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.SystemFeaturesTest#testCameraFeatures" />
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.SystemFeaturesTest#testSensorFeatures" />
+  <option name="compatibility:exclude-filter"
+      value="CtsContentTestCases android.content.pm.cts.InstallSessionParamsUnitTest#checkSessionParams[11]" />
+  <option name="compatibility:exclude-filter"
+          value="CtsContentTestCases android.content.pm.cts.ChecksumsTest#testFixedFSVerityDefaultChecksumsIncremental"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsContentTestCases android.content.pm.cts.ChecksumsTest#testFixedFSVerityDefaultChecksums"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsGraphicsTestCases android.graphics.cts.VulkanFeaturesTest#testVulkanHardwareFeatures"/>
+
+  <!-- documentsui dependent tests -->
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.DownloadManagerTest#testDownload_onMediaStoreDownloadsDeleted"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.StrictModeTest#testVmPenaltyListener" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.StrictModeTest#testContentUriWithoutPermission" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.StrictModeTest#testFileUriExposure" />
+
+  <!-- ime dependent tests -->
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.ActivityKeyboardShortcutsTest#testRequestShowKeyboardShortcuts"/>
+  <option name="compatibility:exclude-filter" value="CtsInputMethodTestCases"/>
+
+  <!-- systemui dependent tests -->
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_setSuppressBubble"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_setSuppressBubble_dismissLocusActivity"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_checkActivityFlagsDocumentLaunchMode"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_checkIsBubbled_pendingIntent"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_checkIsBubbled_shortcut"/>
+  <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationManagerBubbleTest"/>
+  <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationTemplateTest#testPromoteBigPicture_withLargeIcon"/>         |
+  <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationTemplateTest#testPromoteBigPicture_withBigLargeIcon"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.AppHibernationIntegrationTest#testUnusedApp_getsForceStopped" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.AutoRevokeTest#testInstallGrants_notRevokedImmediately" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.AutoRevokeTest#testAutoRevoke_userAllowlisting" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.AutoRevokeTest#testUnusedApp_getsPermissionRevoked" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.AutoRevokeTest#testUnusedApp_uninstallApp" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.ViewTest#testGetWindowVisibleDisplayFrame" />
+  <option name="compatibility:exclude-filter"
+      value="CtsWidgetTestCases android.widget.cts.ToastTest" />
+  <option name="compatibility:exclude-filter" value="CtsSliceTestCases android.slice.cts.SlicePermissionsTest#testPermissionIntent"/>
+
+  <!-- other apps dependent tests -->
+  <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityManagerTest#testHomeVisibilityListener"/>
+  <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityManagerFgsBgStartTest#testVisibleActivityGracePeriod"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsContentTestCases android.content.cts.AvailableIntentsTest"/>
+  <option name="compatibility:exclude-filter"
+          value="CtsGraphicsTestCases android.graphics.cts.SystemPaletteTest#testThemeStyles"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.AppHibernationIntegrationTest#testAppInfo_RemovePermissionsAndFreeUpSpaceToggleExists" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.CompanionDeviceManagerTest#testProfiles" />
+  <option name="compatibility:exclude-filter"
+      value="CtsOsTestCases android.os.cts.CompanionDeviceManagerTest#testRequestNotifications" />
+  <option name="compatibility:exclude-filter"
+      value="CtsLocationFineTestCases android.location.cts.fine.ScanningSettingsTest#testWifiScanningSettings" />
+  <option name="compatibility:exclude-filter"
+      value="CtsLocationFineTestCases android.location.cts.fine.ScanningSettingsTest#testBleScanningSettings" />
+  <option name="compatibility:exclude-filter"
+      value="CtsVoiceInteractionTestCases android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success" />
+
+  <!-- hardware renderer dependent tests -->
+  <option name="compatibility:exclude-filter"
+      value="CtsDisplayTestCases android.display.cts.VirtualDisplayTest#testUntrustedSysDecorVirtualDisplay" />
+  <option name="compatibility:exclude-filter"
+      value="CtsGraphicsTestCases android.graphics.cts.BasicVulkanGpuTest" />
+  <option name="compatibility:exclude-filter"
+      value="CtsGraphicsTestCases android.graphics.cts.BitmapTest#testCreateBitmap_Picture_immutable" />
+  <option name="compatibility:exclude-filter"
+      value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedImageDrawableTest#testRepeatCounts" />
+  <option name="compatibility:exclude-filter"
+      value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedImageDrawableTest#testAddCallbackAfterStart" />
+  <option name="compatibility:exclude-filter"
+      value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedImageDrawableTest#testLifeCycle" />
+  <option name="compatibility:exclude-filter"
+          value="CtsGraphicsTestCases android.graphics.cts.HardwareRendererTest#isDrawingEnabled_defaultsTrue"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsNativeHardwareTestCases" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.ASurfaceControlTest" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.ASurfaceControlBackPressureTest" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.FrameMetricsListenerTest" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.PixelCopyTest" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.TextureViewTest#testSamplingWithTransform" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.TextureViewTest#testTransformScale" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.TextureViewTest#testRotateScale" />
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.TextureViewTest#testFirstFrames" />
+  <option name="compatibility:exclude-filter"
+      value="CtsWidgetTestCases android.widget.cts.MagnifierTest" />
+
+  <!-- screenshot reliant tests -->
+  <option name="compatibility:exclude-filter"
+      value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedVectorDrawableParameterizedTest#testAnimationOnLayer"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedVectorDrawableParameterizedTest#testInfiniteAVD"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.ViewAnimationMatrixTest#testAnimationMatrixAppliedDuringDrawing"/>
+  <option name="compatibility:exclude-filter"
+      value="CtsViewTestCases android.view.cts.ViewAnimationMatrixTest#testAnimationMatrixClearedWithPassingNull"/>
+  <option name="compatibility:exclude-filter" value="CtsUiRenderingTestCases"/>
+  <option name="compatibility:exclude-filter" value="CtsViewTestCases android.view.cts.TextureViewTest#testCropRect"/>
+
+
+</configuration>