Merge "Revert "Temporarily ignoring testSecuredGuestUserResumeToSameUser"" into sc-dev
diff --git a/car-internal-lib/Android.bp b/car-internal-lib/Android.bp
index 9d6aef7..2937bd1 100644
--- a/car-internal-lib/Android.bp
+++ b/car-internal-lib/Android.bp
@@ -28,5 +28,6 @@
         "src/com/android/car/internal/SystemConstants.java",
         "src/com/android/car/internal/ICarServiceHelper.aidl",
         "src/com/android/car/internal/ICarSystemServerClient.aidl",
+        "src/com/android/car/internal/testing/ExcludeFromCodeCoverageGeneratedReport.java",
     ],
 }
diff --git a/car-internal-lib/src/com/android/car/internal/SystemConstants.java b/car-internal-lib/src/com/android/car/internal/SystemConstants.java
index 0025ea7..88356f1 100644
--- a/car-internal-lib/src/com/android/car/internal/SystemConstants.java
+++ b/car-internal-lib/src/com/android/car/internal/SystemConstants.java
@@ -15,6 +15,10 @@
  */
 package com.android.car.internal;
 
+import static com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
+import com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport;
+
 /**
  * Provides common constants for CarService, CarServiceHelperService and other packages.
  */
@@ -22,6 +26,8 @@
 
     public static final String ICAR_SYSTEM_SERVER_CLIENT = "ICarSystemServerClient";
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
+            details = "private constructor")
     private SystemConstants() {
         throw new UnsupportedOperationException("contains only static constants");
     }
diff --git a/car-internal-lib/src/com/android/car/internal/testing/ExcludeFromCodeCoverageGeneratedReport.java b/car-internal-lib/src/com/android/car/internal/testing/ExcludeFromCodeCoverageGeneratedReport.java
new file mode 100644
index 0000000..b13d295
--- /dev/null
+++ b/car-internal-lib/src/com/android/car/internal/testing/ExcludeFromCodeCoverageGeneratedReport.java
@@ -0,0 +1,69 @@
+/*
+ * 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 com.android.car.internal.testing;
+
+import android.annotation.IntDef;
+
+import com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport.Reason;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+// NOTE: it's copied from car-lib but using a different package
+/**
+ * Annotation used to mark code to be excluded from coverage report.
+ */
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
+public @interface ExcludeFromCodeCoverageGeneratedReport {
+
+    // Reason annotation and its associated constant values
+    int DEPRECATED_CODE = 0;
+    int BOILERPLATE_CODE = 1;
+    int DUMP_INFO = 2;
+    int DEBUGGING_CODE = 3;
+
+    @IntDef(prefix = "REASON_", value = {
+            DEPRECATED_CODE,
+            BOILERPLATE_CODE,
+            DUMP_INFO,
+            DEBUGGING_CODE
+    })
+    @interface Reason { }
+
+    /**
+     * The reason explaining why the code is being excluded from the code coverage report.
+     * <p>
+     * Possible reasons to exclude code from coverage report are:
+     * <p><ul>
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DEPRECATED_CODE} to exclude deprecated
+     * code from coverage report
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#BOILERPLATE_CODE} to exclude boilerplate
+     * code like {@link java.lang.Object} methods, {@link android.os.Parcel} methods, etc
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DUMP_INFO} to exclude dump info methods
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DEBUGGING_CODE} to exclude debugging
+     * purpose
+     * code
+     * </ul><p>
+     */
+    @Reason int reason();
+
+    /**
+     * Optional field used to provide extra details about the excluded code (e.g. it can be used to
+     * tag a follow up bug).
+     */
+    String details() default "";
+}
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index 9c11299..84bbc0d 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -86,7 +86,8 @@
     srcs: [
         "src/com/android/car/internal/common/**/*.java",
         "src/com/android/car/internal/common/EventLogTags.logtags",
-    ]
+        "src/com/android/car/internal/ExcludeFromCodeCoverageGeneratedReport.java",
+    ],
 }
 
 java_library {
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index 94162b7..2e83510 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -377,7 +377,12 @@
     public static final int CURRENT_GEAR = 289408001;
     /**
      * Parking brake state.
-     * Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
+     *
+     * <p>PARKING_BRAKE_ON property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
+     * CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
+     * CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}, and returns a Boolean type value.
+     *
+     * <p>Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
      */
     @RequiresPermission(Car.PERMISSION_POWERTRAIN)
     public static final int PARKING_BRAKE_ON = 287310850;
@@ -394,8 +399,16 @@
     @RequiresPermission(Car.PERMISSION_ENERGY)
     public static final int FUEL_LEVEL_LOW = 287310853;
     /**
-     * Night mode
-     * Requires permission: {@link Car#PERMISSION_EXTERIOR_ENVIRONMENT}.
+     * Night mode.
+     *
+     * <p>NIGHT_MODE property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
+     * CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
+     * CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}, and returns a Boolean type value.
+     *
+     * <p>True indicates that the night mode sensor has detected that the car cabin environment has
+     * low light.
+     *
+     * <p>Requires permission: {@link Car#PERMISSION_EXTERIOR_ENVIRONMENT}.
      */
     @RequiresPermission(Car.PERMISSION_EXTERIOR_ENVIRONMENT)
     public static final int NIGHT_MODE = 287310855;
diff --git a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
index 1167810..9761ee1 100644
--- a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
+++ b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
@@ -16,6 +16,8 @@
 
 package android.car.cluster;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE;
+
 import android.annotation.SystemApi;
 import android.car.Car;
 import android.car.CarManagerBase;
@@ -23,6 +25,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
 /**
  * API to work with instrument cluster.
  *
@@ -35,6 +39,7 @@
  */
 @Deprecated
 @SystemApi
+@ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE)
 public class CarInstrumentClusterManager extends CarManagerBase {
     /**
      * @deprecated use {@link android.car.Car#CATEGORY_NAVIGATION} instead
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
index 9687efb..1474fc7 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
@@ -15,11 +15,14 @@
  */
 package android.car.cluster.renderer;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE;
+
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UiThread;
 import android.content.Context;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 
 /**
@@ -30,6 +33,7 @@
  */
 @Deprecated
 @SystemApi
+@ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE)
 public abstract class InstrumentClusterRenderer {
 
     private final Object mLock = new Object();
diff --git a/car-lib/src/android/car/hardware/power/CarPowerPolicy.java b/car-lib/src/android/car/hardware/power/CarPowerPolicy.java
index 99fb245..bbc37eb 100644
--- a/car-lib/src/android/car/hardware/power/CarPowerPolicy.java
+++ b/car-lib/src/android/car/hardware/power/CarPowerPolicy.java
@@ -16,9 +16,12 @@
 
 package android.car.hardware.power;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -186,6 +189,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/hardware/power/CarPowerPolicy.java",
             inputSignatures = "private final @android.annotation.NonNull java.lang.String mPolicyId\nprivate final @android.annotation.NonNull int[] mEnabledComponents\nprivate final @android.annotation.NonNull int[] mDisabledComponents\nclass CarPowerPolicy extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/hardware/power/CarPowerPolicyFilter.java b/car-lib/src/android/car/hardware/power/CarPowerPolicyFilter.java
index 29b8ad6..2fce4b1 100644
--- a/car-lib/src/android/car/hardware/power/CarPowerPolicyFilter.java
+++ b/car-lib/src/android/car/hardware/power/CarPowerPolicyFilter.java
@@ -16,9 +16,12 @@
 
 package android.car.hardware.power;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -165,6 +168,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/hardware/power/CarPowerPolicyFilter.java",
             inputSignatures = "private @android.annotation.NonNull int[] mComponents\nclass CarPowerPolicyFilter extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/input/CustomInputEvent.java b/car-lib/src/android/car/input/CustomInputEvent.java
index fd5412c..c82fb25 100644
--- a/car-lib/src/android/car/input/CustomInputEvent.java
+++ b/car-lib/src/android/car/input/CustomInputEvent.java
@@ -16,11 +16,14 @@
 
 package android.car.input;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -177,6 +180,7 @@
 
     @Override
     @DataClass.Generated.Member
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public String toString() {
         // You can override field toString logic by defining methods like:
         // String fieldNameToString() { ... }
@@ -190,6 +194,7 @@
 
     @Override
     @DataClass.Generated.Member
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public boolean equals(@android.annotation.Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
         // boolean fieldNameEquals(CustomInputEvent other) { ... }
@@ -208,6 +213,7 @@
 
     @Override
     @DataClass.Generated.Member
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public int hashCode() {
         // You can override field hashCode logic by defining methods like:
         // int fieldNameHashCode() { ... }
@@ -232,6 +238,7 @@
 
     @Override
     @DataClass.Generated.Member
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public int describeContents() {
         return 0;
     }
@@ -311,6 +318,7 @@
                     + ".Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true,"
                     + " genAidl=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {
     }
 
diff --git a/car-lib/src/android/car/input/RotaryEvent.java b/car-lib/src/android/car/input/RotaryEvent.java
index 565f039..27f2eee 100644
--- a/car-lib/src/android/car/input/RotaryEvent.java
+++ b/car-lib/src/android/car/input/RotaryEvent.java
@@ -15,11 +15,14 @@
  */
 package android.car.input;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 import java.util.Arrays;
@@ -80,6 +83,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public String toString() {
         return new StringBuilder(128)
                 .append("RotaryEvent{")
@@ -170,6 +174,7 @@
 
     @Override
     @DataClass.Generated.Member
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public boolean equals(@android.annotation.Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
         // boolean fieldNameEquals(RotaryEvent other) { ... }
@@ -188,6 +193,7 @@
 
     @Override
     @DataClass.Generated.Member
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public int hashCode() {
         // You can override field hashCode logic by defining methods like:
         // int fieldNameHashCode() { ... }
@@ -214,6 +220,7 @@
 
     @Override
     @DataClass.Generated.Member
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public int describeContents() { return 0; }
 
     /** @hide */
@@ -259,6 +266,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/input/RotaryEvent.java",
             inputSignatures = "private final @android.car.input.CarInputManager.InputTypeEnum int mInputType\nprivate final  boolean mClockwise\nprivate final @android.annotation.NonNull long[] mUptimeMillisForClicks\npublic  int getNumberOfClicks()\npublic  long getUptimeMillisForClick(int)\npublic @java.lang.Override java.lang.String toString()\nclass RotaryEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/telemetry/CarTelemetryManager.java b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
index 169a05c..3d3cf50 100644
--- a/car-lib/src/android/car/telemetry/CarTelemetryManager.java
+++ b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
@@ -254,7 +254,7 @@
      * The {@link ManifestKey} is used to uniquely identify a manifest. If a manifest of the same
      * name already exists in {@link com.android.car.telemetry.CarTelemetryService}, then the
      * version will be compared. If the version is strictly higher, the existing manifest will be
-     * replaced by the new one.
+     * replaced by the new one. All cache and intermediate results will be cleared if replaced.
      * TODO(b/185420981): Update javadoc after CarTelemetryService has concrete implementation.
      *
      * @param key      the unique key to identify the manifest.
diff --git a/car-lib/src/android/car/watchdog/IoOveruseAlertThreshold.java b/car-lib/src/android/car/watchdog/IoOveruseAlertThreshold.java
index 153e811..a9886ac 100644
--- a/car-lib/src/android/car/watchdog/IoOveruseAlertThreshold.java
+++ b/car-lib/src/android/car/watchdog/IoOveruseAlertThreshold.java
@@ -16,11 +16,14 @@
 
 package android.car.watchdog;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -166,6 +169,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/IoOveruseAlertThreshold.java",
             inputSignatures = "private @android.annotation.SuppressLint long mDurationInSeconds\nprivate  long mWrittenBytesPerSecond\nclass IoOveruseAlertThreshold extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java b/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java
index 7dec00c..608c3c3 100644
--- a/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java
+++ b/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java
@@ -16,10 +16,13 @@
 
 package android.car.watchdog;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 import java.util.List;
@@ -414,6 +417,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java",
             inputSignatures = "private @android.annotation.NonNull android.car.watchdog.PerStateBytes mComponentLevelThresholds\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.car.watchdog.PerStateBytes> mPackageSpecificThresholds\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.car.watchdog.PerStateBytes> mAppCategorySpecificThresholds\nprivate @android.annotation.NonNull java.util.List<android.car.watchdog.IoOveruseAlertThreshold> mSystemWideThresholds\nclass IoOveruseConfiguration extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genBuilder=true, genHiddenConstDefs=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/watchdog/IoOveruseStats.java b/car-lib/src/android/car/watchdog/IoOveruseStats.java
index acb30db..4b5c480 100644
--- a/car-lib/src/android/car/watchdog/IoOveruseStats.java
+++ b/car-lib/src/android/car/watchdog/IoOveruseStats.java
@@ -16,9 +16,12 @@
 
 package android.car.watchdog;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -414,6 +417,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/IoOveruseStats.java",
             inputSignatures = "private  long mStartTime\nprivate  long mDurationInSeconds\nprivate  long mTotalOveruses\nprivate  long mTotalTimesKilled\nprivate  long mTotalBytesWritten\nprivate  boolean mKillableOnOveruse\nprivate @android.annotation.NonNull android.car.watchdog.PerStateBytes mRemainingWriteBytes\nclass IoOveruseStats extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenBuilder=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/watchdog/PackageKillableState.java b/car-lib/src/android/car/watchdog/PackageKillableState.java
index 53eff70..cd9cfda 100644
--- a/car-lib/src/android/car/watchdog/PackageKillableState.java
+++ b/car-lib/src/android/car/watchdog/PackageKillableState.java
@@ -16,11 +16,14 @@
 
 package android.car.watchdog;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -243,6 +246,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/PackageKillableState.java",
             inputSignatures = "public static final  int KILLABLE_STATE_YES\npublic static final  int KILLABLE_STATE_NO\npublic static final  int KILLABLE_STATE_NEVER\nprivate @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.car.watchdog.PackageKillableState.KillableState int mKillableState\nclass PackageKillableState extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/watchdog/PerStateBytes.java b/car-lib/src/android/car/watchdog/PerStateBytes.java
index 421f607..9343f93 100644
--- a/car-lib/src/android/car/watchdog/PerStateBytes.java
+++ b/car-lib/src/android/car/watchdog/PerStateBytes.java
@@ -16,8 +16,11 @@
 
 package android.car.watchdog;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -167,6 +170,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/PerStateBytes.java",
             inputSignatures = "private  long mForegroundModeBytes\nprivate  long mBackgroundModeBytes\nprivate  long mGarageModeBytes\nclass PerStateBytes extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java b/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java
index b179590..03a972a 100644
--- a/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java
+++ b/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java
@@ -16,6 +16,8 @@
 
 package android.car.watchdog;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -23,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.os.Parcelable;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 import java.lang.annotation.Retention;
@@ -525,6 +528,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java",
             inputSignatures = "public static final  int COMPONENT_TYPE_SYSTEM\npublic static final  int COMPONENT_TYPE_VENDOR\npublic static final  int COMPONENT_TYPE_THIRD_PARTY\npublic static final  java.lang.String APPLICATION_CATEGORY_TYPE_MAPS\npublic static final  java.lang.String APPLICATION_CATEGORY_TYPE_MEDIA\nprivate @android.car.watchdog.ResourceOveruseConfiguration.ComponentType int mComponentType\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mSafeToKillPackages\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mVendorPackagePrefixes\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mPackagesToAppCategoryTypes\nprivate @android.annotation.Nullable android.car.watchdog.IoOveruseConfiguration mIoOveruseConfiguration\nclass ResourceOveruseConfiguration extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genBuilder=true, genHiddenConstDefs=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/android/car/watchdog/ResourceOveruseStats.java b/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
index c57acfd..992cff2 100644
--- a/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
+++ b/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
@@ -16,11 +16,14 @@
 
 package android.car.watchdog;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.DataClass;
 
 /**
@@ -256,6 +259,7 @@
             sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/ResourceOveruseStats.java",
             inputSignatures = "private @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull android.os.UserHandle mUserHandle\nprivate @android.annotation.Nullable android.car.watchdog.IoOveruseStats mIoOveruseStats\nclass ResourceOveruseStats extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenBuilder=true)")
     @Deprecated
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private void __metadata() {}
 
 
diff --git a/car-lib/src/com/android/car/internal/common/CommonConstants.java b/car-lib/src/com/android/car/internal/common/CommonConstants.java
index 82b158a..d4d728e 100644
--- a/car-lib/src/com/android/car/internal/common/CommonConstants.java
+++ b/car-lib/src/com/android/car/internal/common/CommonConstants.java
@@ -16,8 +16,12 @@
 
 package com.android.car.internal.common;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.IntDef;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -28,6 +32,7 @@
  */
 public final class CommonConstants {
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     private CommonConstants() {
         throw new UnsupportedOperationException("contains only static constants");
     }
diff --git a/car-lib/src/com/android/car/internal/common/UserHelperLite.java b/car-lib/src/com/android/car/internal/common/UserHelperLite.java
index 4b43e06..c4b8516 100644
--- a/car-lib/src/com/android/car/internal/common/UserHelperLite.java
+++ b/car-lib/src/com/android/car/internal/common/UserHelperLite.java
@@ -16,11 +16,15 @@
 
 package com.android.car.internal.common;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
 /**
  * Provides user information related helper methods.
  *
@@ -28,6 +32,8 @@
  */
 public final class UserHelperLite {
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
+            details = "private constructor")
     private UserHelperLite() {
         throw new UnsupportedOperationException("contains only static methods");
     }
diff --git a/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java b/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
index 1d1c399..f3f0475 100644
--- a/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
+++ b/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
@@ -19,6 +19,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.os.UserHandle;
@@ -27,6 +28,7 @@
 import org.mockito.ArgumentMatcher;
 
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Provides custom mockito matcher for Android / Car objects.
@@ -51,6 +53,13 @@
     }
 
     /**
+     * Matches an {@link Intent} for the given action and package name.
+     */
+    public static Intent intentFor(String action, String packageName) {
+        return argThat(new IntentMatcher(action, packageName));
+    }
+
+    /**
      * Matches if a {@link VehiclePropValue} has the given {@code prop}.
      */
     public static VehiclePropValue isProperty(int prop) {
@@ -165,6 +174,30 @@
         }
     }
 
+    private static final class IntentMatcher implements ArgumentMatcher<Intent> {
+
+        private final String mAction;
+        private final String mPackageName;
+
+        private IntentMatcher(String action, String packageName) {
+            mAction = Objects.requireNonNull(action, "action cannot be null");
+            mPackageName = Objects.requireNonNull(packageName, "packageName cannot be null");
+        }
+
+        @Override
+        public boolean matches(Intent argument) {
+            Log.v(TAG, "IntentMatcher: argument=" + argument + ", this=" + this);
+
+            return argument != null && mAction.equals(argument.getAction())
+                    && mPackageName.equals(argument.getPackage());
+        }
+
+        @Override
+        public String toString() {
+            return "intentFor(action=" + mAction + ", pkg=" + mPackageName + ')';
+        }
+    }
+
     private CarArgumentMatchers() {
         throw new UnsupportedOperationException("contains only static methods");
     }
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index e236c7e..8b75bcb 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -138,12 +138,18 @@
         <install-in user-type="SYSTEM" />
     </install-in-user-type>
 
-    <!-- Required StorageManagerService to bind to the ExternalStorageService -->
+    <!-- Required by StorageManagerService to bind to the ExternalStorageService -->
     <install-in-user-type package="com.android.providers.media.module">
         <install-in user-type="FULL" />
         <install-in user-type="SYSTEM" />
     </install-in-user-type>
 
+    <!-- Required by StorageManagerService  -->
+    <install-in-user-type package="com.android.externalstorage">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
     <!-- Needs to run on system user otherwise cannot find available device -->
     <install-in-user-type package="com.android.bluetooth">
         <install-in user-type="FULL" />
@@ -301,25 +307,15 @@
     <install-in-user-type package="com.android.certinstaller">
         <install-in user-type="FULL" />
     </install-in-user-type>
-    <!-- TODO(b/189246976) STOPSHIP Remove for SYSTEM user since this package
-    is needed for SYSTEM user only if the device supports feature_device_admin
-    and it's used by some device owner APIs-->
     <install-in-user-type package="com.android.pacprocessor">
         <install-in user-type="FULL" />
-        <install-in user-type="SYSTEM" />
     </install-in-user-type>
-    <!-- TODO(b/189246976) STOPSHIP Remove for SYSTEM user same as previous
-    package -->
     <install-in-user-type package="com.android.proxyhandler">
         <install-in user-type="FULL" />
-        <install-in user-type="SYSTEM" />
     </install-in-user-type>
     <install-in-user-type package="com.android.vpndialogs">
         <install-in user-type="FULL" />
     </install-in-user-type>
-    <install-in-user-type package="com.android.externalstorage">
-        <install-in user-type="FULL" />
-    </install-in-user-type>
     <install-in-user-type package="com.android.hotspot2.osulogin">
         <install-in user-type="FULL" />
     </install-in-user-type>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index f675d4e..d7c566c 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -22,6 +22,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Enable multi-user. -->
     <bool name="config_enableMultiUserUI">true</bool>
+    <!--  Maximum number of supported users -->
+    <integer name="config_multiuserMaximumUsers">4</integer>
     <!-- Maximum number of users we allow to be running at a time.
          For automotive, background user will be immediately stopped upon user switching but
          up to this many users can be running in garage mode.
diff --git a/cpp/evs/apps/default/Android.bp b/cpp/evs/apps/default/Android.bp
index 3b393e1..4b1fadf 100644
--- a/cpp/evs/apps/default/Android.bp
+++ b/cpp/evs/apps/default/Android.bp
@@ -25,6 +25,7 @@
     srcs: [
         "evs_app.cpp",
         "EvsStateControl.cpp",
+        "EvsStats.cpp",
         "RenderBase.cpp",
         "RenderDirectView.cpp",
         "RenderTopView.cpp",
@@ -50,10 +51,13 @@
         "libhardware",
         "libpng",
         "libcamera_metadata",
+        "android.frameworks.automotive.telemetry-V1-ndk_platform",
         "android.hardware.camera.device@3.2",
         "android.hardware.automotive.evs@1.0",
         "android.hardware.automotive.evs@1.1",
         "android.hardware.automotive.vehicle@2.0",
+        "libcartelemetry-evs-proto",
+        "libprotobuf-cpp-lite",
     ],
 
     static_libs: [
@@ -81,6 +85,20 @@
 
 }
 
+cc_library {
+    name: "libcartelemetry-evs-proto",
+    srcs: [
+        ":cartelemetry-evs-proto-srcs",
+    ],
+    proto: {
+        export_proto_headers: true,
+        type: "lite",
+    },
+    shared_libs: [
+        "libprotobuf-cpp-lite",
+    ],
+}
+
 prebuilt_etc {
     name: "config.json",
 
diff --git a/cpp/evs/apps/default/EvsStateControl.cpp b/cpp/evs/apps/default/EvsStateControl.cpp
index e3d9757..cb5faf1 100644
--- a/cpp/evs/apps/default/EvsStateControl.cpp
+++ b/cpp/evs/apps/default/EvsStateControl.cpp
@@ -44,17 +44,14 @@
             & static_cast<int32_t>(VehiclePropertyType::MASK));
 }
 
-
-EvsStateControl::EvsStateControl(android::sp <IVehicle>       pVnet,
-                                 android::sp <IEvsEnumerator> pEvs,
-                                 android::sp <IEvsDisplay>    pDisplay,
-                                 const ConfigManager&         config) :
-    mVehicle(pVnet),
-    mEvs(pEvs),
-    mDisplay(pDisplay),
-    mConfig(config),
-    mCurrentState(OFF) {
-
+EvsStateControl::EvsStateControl(android::sp<IVehicle> pVnet, android::sp<IEvsEnumerator> pEvs,
+                                 android::sp<IEvsDisplay> pDisplay, const ConfigManager& config) :
+      mVehicle(pVnet),
+      mEvs(pEvs),
+      mDisplay(pDisplay),
+      mConfig(config),
+      mCurrentState(OFF),
+      mEvsStats(EvsStats::build()) {
     // Initialize the property value containers we'll be updating (they'll be zeroed by default)
     static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32,
                   "Unexpected type for GEAR_SELECTION property");
@@ -114,7 +111,6 @@
     LOG(DEBUG) << "State controller ready";
 }
 
-
 bool EvsStateControl::startUpdateLoop() {
     // Create the thread and report success if it gets started
     mRenderThread = std::thread([this](){ updateLoop(); });
@@ -198,6 +194,12 @@
 
                 // Send the finished image back for display
                 mDisplay->returnTargetBufferForDisplay(tgtBuffer);
+
+                if (!mFirstFrameIsDisplayed) {
+                    mFirstFrameIsDisplayed = true;
+                    // returnTargetBufferForDisplay() is finished, the frame should be displayed
+                    mEvsStats.finishComputingFirstFrameLatency(android::uptimeMillis());
+                }
             }
         } else if (run) {
             // No active renderer, so sleep until somebody wakes us with another command
@@ -214,6 +216,9 @@
         mCurrentRenderer->deactivate();
     }
 
+    // If `ICarTelemetry` service was not ready before, we need to try sending data again.
+    mEvsStats.sendCollectedDataBlocking();
+
     printf("Shutting down app due to state control loop ending\n");
     LOG(ERROR) << "Shutting down app due to state control loop ending";
 }
@@ -295,6 +300,9 @@
         return true;
     }
 
+    // Used by CarStats to accurately compute stats, it needs to be close to the beginning.
+    auto desiredStateTimeMillis = android::uptimeMillis();
+
     LOG(DEBUG) << "Switching to state " << desiredState;
     LOG(DEBUG) << "  Current state " << mCurrentState
                << " has " << mCameraList[mCurrentState].size() << " cameras";
@@ -382,5 +390,12 @@
     LOG(INFO) << "Activated state " << desiredState;
     mCurrentState = desiredState;
 
+    mFirstFrameIsDisplayed = false;  // Got a new renderer, mark first frame is not displayed.
+
+    if (mCurrentRenderer != nullptr && desiredState == State::REVERSE) {
+        // Start computing the latency when the evs state changes.
+        mEvsStats.startComputingFirstFrameLatency(desiredStateTimeMillis);
+    }
+
     return true;
 }
diff --git a/cpp/evs/apps/default/EvsStateControl.h b/cpp/evs/apps/default/EvsStateControl.h
index 1953906..d847195 100644
--- a/cpp/evs/apps/default/EvsStateControl.h
+++ b/cpp/evs/apps/default/EvsStateControl.h
@@ -17,18 +17,18 @@
 #ifndef CAR_EVS_APP_EVSSTATECONTROL_H
 #define CAR_EVS_APP_EVSSTATECONTROL_H
 
-#include "StreamHandler.h"
 #include "ConfigManager.h"
+#include "EvsStats.h"
 #include "RenderBase.h"
+#include "StreamHandler.h"
 
-#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
-#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
 #include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
 
 #include <thread>
 
-
 using namespace ::android::hardware::automotive::evs::V1_1;
 using namespace ::android::hardware::automotive::vehicle::V2_0;
 using ::android::hardware::Return;
@@ -112,6 +112,12 @@
     std::mutex                  mLock;
     std::condition_variable     mWakeSignal;
     std::queue<Command>         mCommandQueue;
+
+    EvsStats                    mEvsStats;  // Not thread-safe
+
+    // True if the first frame displayed on the mCurrentRenderer. Resets to false when
+    // mCurrentRenderer changes.
+    bool                        mFirstFrameIsDisplayed;
 };
 
 
diff --git a/cpp/evs/apps/default/EvsStats.cpp b/cpp/evs/apps/default/EvsStats.cpp
new file mode 100644
index 0000000..f43ae75
--- /dev/null
+++ b/cpp/evs/apps/default/EvsStats.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+#include "EvsStats.h"
+
+#include "packages/services/Car/cpp/telemetry/proto/evs.pb.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <utils/SystemClock.h>
+
+#include <vector>
+
+namespace {
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::android::automotive::telemetry::EvsFirstFrameLatency;
+
+// Name of ICarTelemetry service that consumes RVC latency metrics.
+constexpr const char kCarTelemetryServiceName[] =
+        "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+
+const int kCollectedDataSizeLimit = 200;  // arbitrary chosen
+// 10kb limit is imposed by ICarTelemetry, the limit if only for
+// CarData.content vector.
+const int kCarTelemetryMaxDataSizePerWrite = 10 * 1024;
+
+// Defined in packages/services/Car/cpp/telemetry/proto/CarData.proto
+const int kEvsFirstFrameLatencyId = 1;
+}  // namespace
+
+// static
+EvsStats EvsStats::build() {
+    // No need to enable stats if ICarTelemetry is not available.
+    bool enabled = ::AServiceManager_isDeclared(kCarTelemetryServiceName);
+    return EvsStats(enabled);
+}
+
+void EvsStats::startComputingFirstFrameLatency(int64_t startTimeMillis) {
+    mFirstFrameLatencyStartTimeMillis = startTimeMillis;
+}
+
+void EvsStats::finishComputingFirstFrameLatency(int64_t finishTimeMillis) {
+    if (!mEnabled) {
+        return;
+    }
+    if (mFirstFrameLatencyStartTimeMillis == EvsStatsState::NOT_STARTED) {
+        LOG(WARNING) << __func__ << "EvsStats received finishComputingFirstFrameLatency, but "
+                     << "startComputingFirstFrameLatency was not called before.";
+        return;
+    }
+    auto firstFrameLatencyMillis = finishTimeMillis - mFirstFrameLatencyStartTimeMillis;
+    mFirstFrameLatencyStartTimeMillis = EvsStatsState::NOT_STARTED;
+
+    LOG(DEBUG) << __func__ << ": firstFrameLatencyMillis = " << firstFrameLatencyMillis;
+
+    EvsFirstFrameLatency latency;
+    latency.set_start_timestamp_millis(mFirstFrameLatencyStartTimeMillis);
+    latency.set_latency_millis(firstFrameLatencyMillis);
+    std::vector<uint8_t> bytes(latency.ByteSize());
+    latency.SerializeToArray(&bytes[0], latency.ByteSize());
+    CarData msg;
+    msg.id = kEvsFirstFrameLatencyId;
+    msg.content = std::move(bytes);
+
+    if (msg.content.size() > kCarTelemetryMaxDataSizePerWrite) {
+        LOG(WARNING) << __func__ << "finishComputingFirstFrameLatency is trying to store data "
+                     << "with size " << msg.content.size() << ", which is larger than allowed "
+                     << kCarTelemetryMaxDataSizePerWrite;
+        return;
+    }
+
+    mCollectedData.push_back(msg);
+
+    while (mCollectedData.size() > kCollectedDataSizeLimit) {
+        mCollectedData.pop_front();
+    }
+
+    sendCollectedDataUnsafe(/* waitIfNotReady= */ false);
+}
+
+void EvsStats::sendCollectedDataBlocking() {
+    if (!mEnabled || mCollectedData.empty()) {
+        return;
+    }
+    sendCollectedDataUnsafe(/* waitIfNotReady= */ true);
+}
+
+std::shared_ptr<ICarTelemetry> EvsStats::getCarTelemetry(bool waitIfNotReady) {
+    {
+        const std::scoped_lock<std::mutex> lock(mMutex);
+        if (mCarTelemetry != nullptr) {
+            return mCarTelemetry;
+        }
+    }
+
+    AIBinder* binder;
+    if (waitIfNotReady) {
+        binder = ::AServiceManager_getService(kCarTelemetryServiceName);
+    } else {
+        binder = ::AServiceManager_checkService(kCarTelemetryServiceName);
+    }
+
+    if (binder == nullptr) {
+        LOG(WARNING) << __func__ << ": ICarTelemetry is not ready";
+        return nullptr;
+    }
+
+    const std::scoped_lock<std::mutex> lock(mMutex);  // locks until the end of the method
+    mCarTelemetry = ICarTelemetry::fromBinder(ndk::SpAIBinder(binder));
+    auto status = ndk::ScopedAStatus::fromStatus(
+            ::AIBinder_linkToDeath(mCarTelemetry->asBinder().get(), mBinderDeathRecipient.get(),
+                                   this));
+    if (!status.isOk()) {
+        LOG(WARNING) << __func__
+                     << ": Failed to linkToDeath, continuing anyway: " << status.getMessage();
+    }
+    return mCarTelemetry;
+}
+
+void EvsStats::sendCollectedDataUnsafe(bool waitIfNotReady) {
+    std::shared_ptr<ICarTelemetry> telemetry = getCarTelemetry(waitIfNotReady);
+    if (telemetry == nullptr) {
+        LOG(INFO) << __func__ << ": CarTelemetry is not ready, ignoring";
+        return;
+    }
+
+    // Send data chunk by chnk, because Binder has transfer data size limit.
+    // Adds the oldest elements to `sendingData` and tries to push ICarTelemetryService.
+    // If successful, erases then from `mCollectedData`, otherwise leaves them there to try again
+    // later.
+    while (!mCollectedData.empty()) {
+        int sendingDataSizeBytes = 0;
+        std::vector<CarData> sendingData;
+        auto it = mCollectedData.begin();
+        while (it != mCollectedData.end()) {
+            sendingDataSizeBytes += it->content.size();
+            if (sendingDataSizeBytes > kCarTelemetryMaxDataSizePerWrite) {
+                break;
+            }
+            sendingData.push_back(*it);
+            it++;
+        }
+        ndk::ScopedAStatus status = telemetry->write(sendingData);
+        if (!status.isOk()) {
+            LOG(WARNING) << __func__
+                         << "Failed to write data to ICarTelemetry: " << status.getMessage();
+            return;
+        }
+        mCollectedData.erase(mCollectedData.begin(), it);
+    }
+}
+
+// Removes the listener if its binder dies.
+void EvsStats::telemetryBinderDiedImpl() {
+    LOG(WARNING) << __func__ << "ICarTelemetry service died, resetting the state";
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    mCarTelemetry = nullptr;
+}
+
+// static
+void EvsStats::telemetryBinderDied(void* cookie) {
+    // We expect the pointer to be alive as there is only a single instance of
+    // EvsStats and if EvsStats is destructed, then the whole evs_app should be dead too.
+    auto thiz = static_cast<EvsStats*>(cookie);
+    thiz->telemetryBinderDiedImpl();
+}
diff --git a/cpp/evs/apps/default/EvsStats.h b/cpp/evs/apps/default/EvsStats.h
new file mode 100644
index 0000000..0d9d47f
--- /dev/null
+++ b/cpp/evs/apps/default/EvsStats.h
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#ifndef CAR_EVS_APP_EVSSTATS_H
+#define CAR_EVS_APP_EVSSTATS_H
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android/binder_status.h>
+#include <utils/Mutex.h>
+
+#include <deque>
+#include <memory>
+
+// Performs metric computations, sends to `ICarTelemetry`.
+//
+// Not thread-safe. Methods `startComputingFirstFrameLatency`, `finishComputingFirstFrameLatency`
+// and `sendCollectedDataBlocking` must be called from the same thread.
+class EvsStats {
+public:
+    // Instantiates EvsStats.
+    static EvsStats build();
+
+    // Starts computing end-2-end first frame latency: from the time of the event that starts the
+    // camera to the time of the display of the very first frame.
+    // Call this method when an event that enables the camera occurred, e.g. gear shift to REAR.
+    // Param `startTimeMillis` should be `android::uptimeMillis()`.
+    void startComputingFirstFrameLatency(int64_t startTimeMillis);
+
+    // Computes the latency and sends the data to `ICarTelemetry` if the receiving service is up.
+    // Call this method when the first camera frame is displayed on the screen and don't call
+    // after that unless a new computation is started.
+    // Param `finishTimeMillis` should be `android::uptimeMillis()`.
+    void finishComputingFirstFrameLatency(int64_t finishTimeMillis);
+
+    // Sends the collected data to `ICarTelemetry`. Blocks for short amount of time if the service
+    // is unavailable.
+    void sendCollectedDataBlocking();
+
+private:
+    enum EvsStatsState {
+        NOT_STARTED = -1,
+    };
+
+private:
+    EvsStats(bool enabled) :
+          mEnabled(enabled),
+          mBinderDeathRecipient(::AIBinder_DeathRecipient_new(EvsStats::telemetryBinderDied)) {}
+
+    // Death recipient callback that is called when ICarTelemetry dies.
+    // The cookie is a pointer to a EvsStats object.
+    static void telemetryBinderDied(void* cookie);
+
+    void telemetryBinderDiedImpl();
+
+    // Tries sending data if the receiving service is up.
+    // Must be called when both `mEnabled` is true and `mCollectedData` is not empty.
+    //
+    // \param waitIfNotReady - if true, it can block briefly until ICarTelemetry is ready.
+    void sendCollectedDataUnsafe(bool waitIfNotReady);
+
+    // Returns the instance of ICarTelemetry.
+    //
+    // \param waitIfNotReady - if true, it can block briefly until ICarTelemetry is ready.
+    std::shared_ptr<aidl::android::frameworks::automotive::telemetry::ICarTelemetry>
+    getCarTelemetry(bool waitIfNotReady);
+
+    std::mutex mMutex;
+    bool mEnabled;
+    int64_t mFirstFrameLatencyStartTimeMillis = EvsStatsState::NOT_STARTED;
+    // This is a ring buffer
+    std::deque<aidl::android::frameworks::automotive::telemetry::CarData> mCollectedData;
+    std::shared_ptr<aidl::android::frameworks::automotive::telemetry::ICarTelemetry> mCarTelemetry;
+    ndk::ScopedAIBinder_DeathRecipient mBinderDeathRecipient;
+};
+
+#endif  // CAR_EVS_APP_EVSSTATS_H
diff --git a/cpp/evs/sampleDriver/bufferCopy.cpp b/cpp/evs/sampleDriver/bufferCopy.cpp
index d9578e6..098af61 100644
--- a/cpp/evs/sampleDriver/bufferCopy.cpp
+++ b/cpp/evs/sampleDriver/bufferCopy.cpp
@@ -123,14 +123,27 @@
 void fillRGBAFromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) {
     const AHardwareBuffer_Desc* pDesc =
         reinterpret_cast<const AHardwareBuffer_Desc*>(&tgtBuff.buffer.description);
+    // Converts YUY2ToARGB (little endian).  Please note that libyuv uses the
+    // little endian while we're using the big endian in RGB format names.
+    const auto dstStrideInBytes = pDesc->stride * 4;  // 4-byte per pixel
     auto result = libyuv::YUY2ToARGB((const uint8_t*)imgData,
                                      imgStride,             // input stride in bytes
                                      tgt,
-                                     (pDesc->stride << 2),  // output stride in bytes
+                                     dstStrideInBytes,      // output stride in bytes
                                      pDesc->width,
                                      pDesc->height);
     if (result) {
-        LOG(ERROR) << "Failed to convert YUYV to ARGB.";
+        LOG(ERROR) << "Failed to convert YUYV to BGRA.";
+        return;
+    }
+
+    // Swaps R and B pixels to convert BGRA to RGBA in place.
+    // TODO(b/190783702): Consider allocating an extra space to store ARGB data
+    //                    temporarily if below operation is too slow.
+    result = libyuv::ABGRToARGB(tgt, dstStrideInBytes, tgt, dstStrideInBytes,
+                                pDesc->width, pDesc->height);
+    if (result) {
+        LOG(ERROR) << "Failed to convert BGRA to RGBA.";
     }
 }
 
diff --git a/cpp/powerpolicy/sepolicy/private/carpowerpolicy.te b/cpp/powerpolicy/sepolicy/private/carpowerpolicy.te
index a7184ab..4b980f6 100644
--- a/cpp/powerpolicy/sepolicy/private/carpowerpolicy.te
+++ b/cpp/powerpolicy/sepolicy/private/carpowerpolicy.te
@@ -19,3 +19,7 @@
 
 # Allow reading and writing /sys/power/
 allow carpowerpolicyd sysfs_power:file rw_file_perms;
+
+# Allow updating properties to control boot animation
+set_prop(carpowerpolicyd, debug_prop)
+set_prop(carpowerpolicyd, bootanim_system_prop)
diff --git a/cpp/powerpolicy/server/carpowerpolicyd.xml b/cpp/powerpolicy/server/carpowerpolicyd.xml
index e8df893..0d2462f 100644
--- a/cpp/powerpolicy/server/carpowerpolicyd.xml
+++ b/cpp/powerpolicy/server/carpowerpolicyd.xml
@@ -16,6 +16,7 @@
 <manifest version="1.0" type="framework">
     <hal format="aidl">
         <name>android.frameworks.automotive.powerpolicy</name>
+        <version>1</version>
         <interface>
             <name>ICarPowerPolicyServer</name>
             <instance>default</instance>
diff --git a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
index 4c9b260..10005e9 100644
--- a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
+++ b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
@@ -567,7 +567,6 @@
     if (!ret.ok()) {
         ALOGW("Failed to apply power policy: %s", ret.error().message().c_str());
     }
-    mSilentModeHandler.updateKernelSilentMode(isSilent);
 }
 
 bool CarPowerPolicyServer::isRegisteredLocked(const sp<ICarPowerPolicyChangeCallback>& callback) {
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.cpp b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
index 45a7976..c57b6fa 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.cpp
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
@@ -39,6 +39,7 @@
 using ::android::base::GetProperty;
 using ::android::base::ReadFileToString;
 using ::android::base::Result;
+using ::android::base::SetProperty;
 using ::android::base::StringPrintf;
 using ::android::base::Trim;
 using ::android::base::unique_fd;
@@ -46,9 +47,13 @@
 
 namespace {
 
-constexpr const char* kPropertySystemBootReason = "sys.boot.reason";
-constexpr const char* kSilentModeHwStateFilename = "/sys/power/pm_silentmode_hw_state";
-constexpr const char* kKernelSilentModeFilename = "/sys/power/pm_silentmode_kernel";
+constexpr const char kPropertySystemBootReason[] = "sys.boot.reason";
+constexpr const char kSilentModeHwStateFilename[] = "/sys/power/pm_silentmode_hw_state";
+constexpr const char kKernelSilentModeFilename[] = "/sys/power/pm_silentmode_kernel";
+// To prevent boot animation from being started.
+constexpr const char kPropertyNoBootAnimation[] = "debug.sf.nobootanimation";
+// To stop boot animation while it is being played.
+constexpr const char kPropertyBootAnimationExit[] = "service.bootanim.exit";
 constexpr int kEventBufferSize = 512;
 
 bool fileExists(const char* filename) {
@@ -76,9 +81,7 @@
         mSilentModeByHwState = false;
     }
     if (mForcedMode) {
-        if (auto ret = updateKernelSilentModeInternal(mSilentModeByHwState); !ret.ok()) {
-            ALOGW("Failed to update kernel silent mode: %s", ret.error().message().c_str());
-        }
+        handleSilentModeChange(mSilentModeByHwState);
         mSilentModeChangeHandler->notifySilentModeChange(mSilentModeByHwState);
         ALOGI("Now in forced mode: monitoring %s is disabled", kSilentModeHwStateFilename);
     } else {
@@ -95,27 +98,6 @@
     return mSilentModeByHwState;
 }
 
-Result<void> SilentModeHandler::updateKernelSilentMode(bool silent) {
-    if (mForcedMode) {
-        return Error() << "Cannot update " << mKernelSilentModeFilename << " in forced mode";
-    }
-    return updateKernelSilentModeInternal(silent);
-}
-
-Result<void> SilentModeHandler::updateKernelSilentModeInternal(bool silent) {
-    int fd = open(mKernelSilentModeFilename.c_str(), O_WRONLY | O_NONBLOCK);
-    if (fd < 0) {
-        return Error() << "Failed to open " << mKernelSilentModeFilename;
-    }
-    Result<void> status = {};
-    if (const auto& value = silent ? kValueSilentMode : kValueNonSilentMode;
-        !WriteStringToFd(value, fd)) {
-        status = Error() << "Failed to write " << value << " to fd " << fd;
-    }
-    close(fd);
-    return status;
-}
-
 void SilentModeHandler::stopMonitoringSilentModeHwState(bool shouldWaitThread) {
     if (mIsMonitoring) {
         mIsMonitoring = false;
@@ -226,10 +208,49 @@
     if (newSilentMode != oldSilentMode) {
         ALOGI("%s is set to %s", mSilentModeHwStateFilename.c_str(),
               newSilentMode ? "silent" : "non-silent");
+        handleSilentModeChange(newSilentMode);
         mSilentModeChangeHandler->notifySilentModeChange(newSilentMode);
     }
 }
 
+void SilentModeHandler::handleSilentModeChange(bool silent) {
+    if (auto ret = updateKernelSilentMode(silent); !ret.ok()) {
+        ALOGW("Failed to update kernel silent mode: %s", ret.error().message().c_str());
+    }
+    if (auto ret = enableBootAnimation(!silent); !ret.ok()) {
+        ALOGW("Failed to %s boot animation: %s", mSilentModeByHwState ? "disabling" : "enabling",
+              ret.error().message().c_str());
+    }
+}
+
+Result<void> SilentModeHandler::enableBootAnimation(bool enabled) {
+    const std::string value = enabled ? "0" : "1";
+    if (!SetProperty(kPropertyNoBootAnimation, value)) {
+        return Error() << "Failed to set " << kPropertyNoBootAnimation << " property to " << value;
+    }
+    if (!enabled) {
+        if (!SetProperty(kPropertyBootAnimationExit, value)) {
+            return Error() << "Failed to set " << kPropertyBootAnimationExit << " property to "
+                           << value;
+        }
+    }
+    return {};
+}
+
+Result<void> SilentModeHandler::updateKernelSilentMode(bool silent) {
+    int fd = open(mKernelSilentModeFilename.c_str(), O_WRONLY | O_NONBLOCK);
+    if (fd < 0) {
+        return Error() << "Failed to open " << mKernelSilentModeFilename;
+    }
+    Result<void> status = {};
+    if (const auto& value = silent ? kValueSilentMode : kValueNonSilentMode;
+        !WriteStringToFd(value, fd)) {
+        status = Error() << "Failed to write " << value << " to fd " << fd;
+    }
+    close(fd);
+    return status;
+}
+
 }  // namespace powerpolicy
 }  // namespace automotive
 }  // namespace frameworks
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.h b/cpp/powerpolicy/server/src/SilentModeHandler.h
index 44a2835..9fae109 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.h
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.h
@@ -60,17 +60,17 @@
     void release();
     // Returns the current Silent Mode.
     bool isSilentMode();
-    // Write the value corresponding to the given silent state to /sys/power/pm_silentmode_kernel.
-    android::base::Result<void> updateKernelSilentMode(bool silent);
     // Stops monitoring the change on /sys/power/pm_silentmode_hw_state.
     void stopMonitoringSilentModeHwState(bool shouldWaitThread);
     // Dumps the internal state.
     android::base::Result<void> dump(int fd, const Vector<String16>& args);
 
 private:
-    android::base::Result<void> updateKernelSilentModeInternal(bool silent);
+    android::base::Result<void> updateKernelSilentMode(bool silent);
     void startMonitoringSilentModeHwState();
     void handleSilentModeHwStateChange();
+    void handleSilentModeChange(bool silent);
+    android::base::Result<void> enableBootAnimation(bool enabled);
 
     android::Mutex mMutex;
     bool mSilentModeByHwState GUARDED_BY(mMutex);
diff --git a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
index a090b3a..1eaec81 100644
--- a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
+++ b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
@@ -93,6 +93,8 @@
         return Trim(value);
     }
 
+    void updateKernelSilentMode(bool isSilent) { mHandler->updateKernelSilentMode(isSilent); }
+
 private:
     SilentModeHandler* mHandler;
     TemporaryFile mFileSilentModeHwState;
@@ -176,12 +178,12 @@
     handlerPeer.injectBootReason(kBootReasonNormal);
     handlerPeer.init();
 
-    handler.updateKernelSilentMode(true);
+    handlerPeer.updateKernelSilentMode(true);
 
     ASSERT_EQ(handlerPeer.readKernelSilentMode(), kValueSilentMode)
             << "Kernel silent mode file should have 1";
 
-    handler.updateKernelSilentMode(false);
+    handlerPeer.updateKernelSilentMode(false);
 
     ASSERT_EQ(handlerPeer.readKernelSilentMode(), kValueNonSilentMode)
             << "Kernel silent mode file should have 0";
diff --git a/cpp/telemetry/proto/Android.bp b/cpp/telemetry/proto/Android.bp
new file mode 100644
index 0000000..862a537
--- /dev/null
+++ b/cpp/telemetry/proto/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "cartelemetry-evs-proto-srcs",
+    srcs: [
+        "evs.proto",
+    ],
+}
diff --git a/cpp/telemetry/proto/CarData.proto b/cpp/telemetry/proto/CarData.proto
new file mode 100644
index 0000000..96e8c2e
--- /dev/null
+++ b/cpp/telemetry/proto/CarData.proto
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+/**
+ * This file contains all the known CarData messages used in AAOS. Some messages
+ * might be defined in OEM's private repo, the owners of those messages must
+ * make sure the CarData IDs are not colliding.
+ *
+ * All the messages must be defined in the CarData message and given a unique
+ * ID.
+ */
+
+syntax = "proto2";
+
+package android.automotive.telemetry;
+option java_package = "com.android.automotive.telemetry";
+option java_outer_classname = "CarDataProto";
+
+import "packages/services/Car/cpp/telemetry/proto/evs.proto";
+
+message CarData {
+  oneof pushed {
+    EvsFirstFrameLatency evs_first_frame_latency = 1;
+  }
+
+  // DO NOT USE field numbers above 100,000 in AOSP.
+  // Field numbers 100,000 - 199,999 are reserved for non-AOSP (e.g. OEMs) to
+  // use. Field numbers 200,000 and above are reserved for future use; do not
+  // use them at all.
+}
diff --git a/cpp/telemetry/proto/evs.proto b/cpp/telemetry/proto/evs.proto
new file mode 100644
index 0000000..c39434f
--- /dev/null
+++ b/cpp/telemetry/proto/evs.proto
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/**
+ * This file contains all the CarData messages used in EVS related processes.
+ * It's preferred to group CarData messages used in a single process or service
+ * to improve performance and memory usage.
+ *
+ * All the messages must be defined in the CarData message and given a unique
+ * ID.
+ */
+
+syntax = "proto2";
+
+package android.automotive.telemetry;
+option java_package = "com.android.automotive.telemetry";
+option java_outer_classname = "EvsProto";
+
+message EvsFirstFrameLatency {
+  // Latency measurement start time since boot.
+  optional int64 start_timestamp_millis = 1;
+
+  // end-2-end latency since gear shifted to reverse and the
+  // first frame from camera is displayed on the screen.
+  optional int64 latency_millis = 2;
+}
diff --git a/cpp/watchdog/server/carwatchdogd.xml b/cpp/watchdog/server/carwatchdogd.xml
index c0c8933..c7adc27 100644
--- a/cpp/watchdog/server/carwatchdogd.xml
+++ b/cpp/watchdog/server/carwatchdogd.xml
@@ -16,6 +16,7 @@
 <manifest version="1.0" type="framework">
     <hal format="aidl">
         <name>android.automotive.watchdog</name>
+        <version>3</version>
         <interface>
             <name>ICarWatchdog</name>
             <instance>default</instance>
diff --git a/cpp/watchdog/server/src/OveruseConfigurationXmlHelper.cpp b/cpp/watchdog/server/src/OveruseConfigurationXmlHelper.cpp
index 308bb8e..a3bf4a9 100644
--- a/cpp/watchdog/server/src/OveruseConfigurationXmlHelper.cpp
+++ b/cpp/watchdog/server/src/OveruseConfigurationXmlHelper.cpp
@@ -59,6 +59,7 @@
 using ::android::base::Trim;
 using ::android::binder::Status;
 using ::tinyxml2::XML_SUCCESS;
+using ::tinyxml2::XMLDeclaration;
 using ::tinyxml2::XMLDocument;
 using ::tinyxml2::XMLElement;
 
@@ -95,6 +96,8 @@
 
 constexpr const char kAttrId[] = "id";
 constexpr const char kAttrType[] = "type";
+constexpr const char kAttrVersion[] = "version";
+constexpr const char kVersionNumber[] = "1.0";
 
 Result<const XMLElement*> readExactlyOneElement(const char* tag, const XMLElement* rootElement) {
     const XMLElement* element = rootElement->FirstChildElement(tag);
@@ -432,6 +435,249 @@
     return configuration;
 }
 
+Result<void> writeComponentType(ComponentType componentType, XMLElement* rootElement) {
+    XMLElement* childElement = rootElement->InsertNewChildElement(kTagComponentType);
+    if (!childElement) {
+        return Error() << "Failed to insert new child element with tag '" << kTagComponentType
+                       << "'";
+    }
+    childElement->SetText(toString(componentType).c_str());
+    return {};
+}
+
+Result<void> writeSafeToKillPackages(const std::vector<std::string>& safeToKillPackages,
+                                     XMLElement* rootElement) {
+    if (safeToKillPackages.empty()) {
+        return {};
+    }
+    XMLElement* outerElement = rootElement->InsertNewChildElement(kTagSafeToKillPackages);
+    if (!outerElement) {
+        return Error() << "Failed to insert new child element with tag '" << kTagSafeToKillPackages
+                       << "'";
+    }
+    for (const auto& package : safeToKillPackages) {
+        XMLElement* innerElement = outerElement->InsertNewChildElement(kTagPackage);
+        if (!innerElement) {
+            return Error() << "Failed to insert new child element with tag '" << kTagPackage << "'";
+        }
+        innerElement->SetText(package.c_str());
+    }
+    return {};
+}
+
+Result<void> writeVendorPackagePrefixes(const std::vector<std::string>& vendorPackagePrefixes,
+                                        XMLElement* rootElement) {
+    if (vendorPackagePrefixes.empty()) {
+        return {};
+    }
+    XMLElement* outerElement = rootElement->InsertNewChildElement(kTagVendorPackagePrefixes);
+    if (!outerElement) {
+        return Error() << "Failed to insert new child element with tag '"
+                       << kTagVendorPackagePrefixes << "'";
+    }
+    for (const auto& packagePrefix : vendorPackagePrefixes) {
+        XMLElement* innerElement = outerElement->InsertNewChildElement(kTagPackagePrefix);
+        if (!innerElement) {
+            return Error() << "Failed to insert new child element with tag '" << kTagPackagePrefix
+                           << "'";
+        }
+        innerElement->SetText(packagePrefix.c_str());
+    }
+    return {};
+}
+
+Result<void> writePackageToAppCategoryTypes(const std::vector<PackageMetadata>& packageMetadata,
+                                            XMLElement* rootElement) {
+    if (packageMetadata.empty()) {
+        return {};
+    }
+    XMLElement* outerElement = rootElement->InsertNewChildElement(kTagPackageToAppCategoryTypes);
+    if (!outerElement) {
+        return Error() << "Failed to insert new child element with tag '"
+                       << kTagPackageToAppCategoryTypes << "'";
+    }
+    for (const auto& meta : packageMetadata) {
+        XMLElement* innerElement = outerElement->InsertNewChildElement(kTagPackageAppCategory);
+        if (!innerElement) {
+            return Error() << "Failed to insert new child element with tag '"
+                           << kTagPackageAppCategory << "'";
+        }
+        innerElement->SetAttribute(kAttrType, toString(meta.appCategoryType).c_str());
+        innerElement->SetText(meta.packageName.c_str());
+    }
+    return {};
+}
+
+Result<void> writePerStateBytes(const PerStateBytes& perStateBytes, XMLElement* rootElement) {
+    const auto writeStateElement = [&](const char* state, int64_t value) -> Result<void> {
+        XMLElement* childElement = rootElement->InsertNewChildElement(kTagState);
+        if (!childElement) {
+            return Error() << "Failed to insert new child element with tag '" << kTagState << "'";
+        }
+        childElement->SetAttribute(kAttrId, state);
+        childElement->SetText(value);
+        return {};
+    };
+    if (const auto result = writeStateElement(kStateIdForegroundMode,
+                                              perStateBytes.foregroundBytes / kOneMegaByte);
+        !result.ok()) {
+        return Error() << "Failed to write bytes for state '" << kStateIdForegroundMode
+                       << "': " << result.error();
+    }
+    if (const auto result = writeStateElement(kStateIdBackgroundMode,
+                                              perStateBytes.backgroundBytes / kOneMegaByte);
+        !result.ok()) {
+        return Error() << "Failed to write bytes for state '" << kStateIdBackgroundMode
+                       << "': " << result.error();
+    }
+    if (const auto result =
+                writeStateElement(kStateIdGarageMode, perStateBytes.garageModeBytes / kOneMegaByte);
+        !result.ok()) {
+        return Error() << "Failed to write bytes for state '" << kStateIdGarageMode
+                       << "': " << result.error();
+    }
+    return {};
+}
+
+Result<void> writeComponentLevelThresholds(const PerStateIoOveruseThreshold& thresholds,
+                                           XMLElement* rootElement) {
+    XMLElement* childElement = rootElement->InsertNewChildElement(kTagComponentLevelThresholds);
+    if (!childElement) {
+        return Error() << "Failed to insert new child element with tag '"
+                       << kTagComponentLevelThresholds << "'";
+    }
+    if (const auto result = writePerStateBytes(thresholds.perStateWriteBytes, childElement);
+        !result.ok()) {
+        return Error() << "Failed to write per-state bytes: " << result.error();
+    }
+    return {};
+}
+
+Result<void> writePerStateThresholds(const PerStateIoOveruseThreshold& thresholds,
+                                     XMLElement* rootElement) {
+    XMLElement* childElement = rootElement->InsertNewChildElement(kTagPerStateThreshold);
+    if (!childElement) {
+        return Error() << "Failed to insert new child element with tag '" << kTagPerStateThreshold
+                       << "'";
+    }
+    childElement->SetAttribute(kAttrId, thresholds.name.c_str());
+    if (const auto result = writePerStateBytes(thresholds.perStateWriteBytes, childElement);
+        !result.ok()) {
+        return Error() << "Failed to write per-state bytes: " << result.error();
+    }
+    return {};
+}
+
+Result<void> writePackageSpecificThresholds(
+        const std::vector<PerStateIoOveruseThreshold>& thresholds, XMLElement* rootElement) {
+    XMLElement* childElement = rootElement->InsertNewChildElement(kTagPackageSpecificThresholds);
+    if (!childElement) {
+        return Error() << "Failed to insert new child element with tag '"
+                       << kTagPackageSpecificThresholds << "'";
+    }
+    for (const auto threshold : thresholds) {
+        if (const auto result = writePerStateThresholds(threshold, childElement); !result.ok()) {
+            return Error() << "Failed to write per-state thresholds for '" << threshold.name
+                           << "': " << result.error();
+        }
+    }
+    return {};
+}
+
+Result<void> writeAppCategorySpecificThresholds(
+        const std::vector<PerStateIoOveruseThreshold>& thresholds, XMLElement* rootElement) {
+    XMLElement* childElement =
+            rootElement->InsertNewChildElement(kTagAppCategorySpecificThresholds);
+    if (!childElement) {
+        return Error() << "Failed to insert new child element with tag '"
+                       << kTagAppCategorySpecificThresholds << "'";
+    }
+    for (const auto threshold : thresholds) {
+        if (const auto result = writePerStateThresholds(threshold, childElement); !result.ok()) {
+            return Error() << "Failed to write per-state thresholds for '" << threshold.name
+                           << "': " << result.error();
+        }
+    }
+    return {};
+}
+
+Result<void> writeAlertThresholds(const IoOveruseAlertThreshold& alertThresholds,
+                                  XMLElement* rootElement) {
+    XMLElement* outerElement = rootElement->InsertNewChildElement(kTagAlertThreshold);
+    if (!outerElement) {
+        return Error() << "Failed to insert new child element with tag '" << kTagAlertThreshold
+                       << "'";
+    }
+    const auto writeParamElement = [&](const char* param, int64_t value) -> Result<void> {
+        XMLElement* innerElement = outerElement->InsertNewChildElement(kTagParam);
+        if (!innerElement) {
+            return Error() << "Failed to insert new child element with tag '" << kTagParam << "'";
+        }
+        innerElement->SetAttribute(kAttrId, param);
+        innerElement->SetText(value);
+        return {};
+    };
+    if (const auto result =
+                writeParamElement(kParamIdDurationSeconds, alertThresholds.durationInSeconds);
+        !result.ok()) {
+        return Error() << "Failed to write duration for param '" << kParamIdDurationSeconds
+                       << "': " << result.error();
+    }
+    if (const auto result = writeParamElement(kParamIdWrittenBytesPerSecond,
+                                              alertThresholds.writtenBytesPerSecond);
+        !result.ok()) {
+        return Error() << "Failed to write bps for param '" << kParamIdWrittenBytesPerSecond
+                       << "': " << result.error();
+    }
+    return {};
+}
+
+Result<void> writeSystemWideThresholds(const std::vector<IoOveruseAlertThreshold>& thresholds,
+                                       XMLElement* rootElement) {
+    XMLElement* childElement = rootElement->InsertNewChildElement(kTagSystemWideThresholds);
+    if (!childElement) {
+        return Error() << "Failed to insert new child element with tag '"
+                       << kTagSystemWideThresholds << "'";
+    }
+    for (const auto threshold : thresholds) {
+        if (const auto result = writeAlertThresholds(threshold, childElement); !result.ok()) {
+            return Error() << "Failed to write I/O overuse alert thresholds:" << result.error();
+        }
+    }
+    return {};
+}
+
+Result<void> writeIoOveruseConfiguration(const IoOveruseConfiguration& configuration,
+                                         XMLElement* rootElement) {
+    XMLElement* childElement = rootElement->InsertNewChildElement(kTagIoOveruseConfiguration);
+    if (!childElement) {
+        return Error() << "Failed to insert new child element with tag '"
+                       << kTagIoOveruseConfiguration << "'";
+    }
+    if (const auto result =
+                writeComponentLevelThresholds(configuration.componentLevelThresholds, childElement);
+        !result.ok()) {
+        return Error() << "Failed to write component-wide thresholds: " << result.error();
+    }
+    if (const auto result = writePackageSpecificThresholds(configuration.packageSpecificThresholds,
+                                                           childElement);
+        !result.ok()) {
+        return Error() << "Failed to write package specific thresholds: " << result.error();
+    }
+    if (const auto result =
+                writeAppCategorySpecificThresholds(configuration.categorySpecificThresholds,
+                                                   childElement);
+        !result.ok()) {
+        return Error() << "Failed to write app category specific thresholds: " << result.error();
+    }
+    if (const auto result =
+                writeSystemWideThresholds(configuration.systemWideThresholds, childElement);
+        !result.ok()) {
+        return Error() << "Failed to write system-wide thresholds: " << result.error();
+    }
+    return {};
+}
+
 }  // namespace
 
 Result<ResourceOveruseConfiguration> OveruseConfigurationXmlHelper::parseXmlFile(
@@ -478,9 +724,54 @@
 }
 
 Result<void> OveruseConfigurationXmlHelper::writeXmlFile(
-        [[maybe_unused]] const ResourceOveruseConfiguration& configuration,
-        [[maybe_unused]] const char* filePath) {
-    // TODO(b/185287136): Write the configuration to file.
+        const ResourceOveruseConfiguration& configuration, const char* filePath) {
+    XMLDocument xmlDoc;
+    if (XMLDeclaration* declaration = xmlDoc.NewDeclaration(); declaration) {
+        xmlDoc.InsertEndChild(declaration);
+    } else {
+        return Error() << "Failed to create new xml declaration";
+    }
+    XMLElement* rootElement = xmlDoc.NewElement(kTagResourceOveruseConfiguration);
+    if (!rootElement) {
+        return Error() << "Failed to create new xml element for tag '"
+                       << kTagResourceOveruseConfiguration << "'";
+    }
+    rootElement->SetAttribute(kAttrVersion, kVersionNumber);
+    xmlDoc.InsertEndChild(rootElement);
+    if (const auto result = writeComponentType(configuration.componentType, rootElement);
+        !result.ok()) {
+        return Error() << "Failed to write component type: " << result.error();
+    }
+    if (const auto result = writeSafeToKillPackages(configuration.safeToKillPackages, rootElement);
+        !result.ok()) {
+        return Error() << "Failed to write safe-to-kill packages: " << result.error();
+    }
+    if (const auto result =
+                writeVendorPackagePrefixes(configuration.vendorPackagePrefixes, rootElement);
+        !result.ok()) {
+        return Error() << "Failed to write vendor package prefixes: " << result.error();
+    }
+    if (const auto result =
+                writePackageToAppCategoryTypes(configuration.packageMetadata, rootElement);
+        !result.ok()) {
+        return Error() << "Failed to write package to app category types: " << result.error();
+    }
+    if (configuration.resourceSpecificConfigurations.size() != 1 ||
+        configuration.resourceSpecificConfigurations[0].getTag() !=
+                ResourceSpecificConfiguration::ioOveruseConfiguration) {
+        return Error() << "Must provide exactly one I/O overuse configuration";
+    }
+    IoOveruseConfiguration ioOveruseConfig =
+            configuration.resourceSpecificConfigurations[0]
+                    .get<ResourceSpecificConfiguration::ioOveruseConfiguration>();
+    if (const auto result = writeIoOveruseConfiguration(ioOveruseConfig, rootElement);
+        !result.ok()) {
+        return Error() << "Failed to write I/O overuse configuration: " << result.error();
+    }
+    if (const auto xmlError = xmlDoc.SaveFile(filePath); xmlError != XML_SUCCESS) {
+        return Error() << "Failed to write XML configuration to file '" << filePath
+                       << "': " << XMLDocument::ErrorIDToName(xmlError);
+    }
     return {};
 }
 
diff --git a/cpp/watchdog/server/tests/OveruseConfigurationXmlHelperTest.cpp b/cpp/watchdog/server/tests/OveruseConfigurationXmlHelperTest.cpp
index 88006d7..9e45fd0 100644
--- a/cpp/watchdog/server/tests/OveruseConfigurationXmlHelperTest.cpp
+++ b/cpp/watchdog/server/tests/OveruseConfigurationXmlHelperTest.cpp
@@ -165,6 +165,138 @@
     }
 }
 
+TEST(OveruseConfigurationXmlHelperTest, TestWriteXmlFileWithSystemConfiguration) {
+    auto ioConfig = constructIoOveruseConfig(
+            /*componentLevel=*/toPerStateIoOveruseThreshold(ComponentType::SYSTEM,
+                                                            300 * kOneMegaByte, 150 * kOneMegaByte,
+                                                            500 * kOneMegaByte),
+            /*packageSpecific=*/
+            {toPerStateIoOveruseThreshold("system.package.C", 400 * kOneMegaByte,
+                                          100 * kOneMegaByte, 200 * kOneMegaByte),
+             toPerStateIoOveruseThreshold("system.package.D", 1024 * kOneMegaByte,
+                                          500 * kOneMegaByte, 2048 * kOneMegaByte)},
+            /*categorySpecific=*/{},
+            /*systemWide=*/{toIoOveruseAlertThreshold(10, 200), toIoOveruseAlertThreshold(5, 50)});
+    ResourceOveruseConfiguration expected =
+            constructResourceOveruseConfig(ComponentType::SYSTEM,
+                                           /*safeToKill=*/{"system.package.A", "system.package.B"},
+                                           /*vendorPrefixes=*/{},
+                                           /*packageMetadata=*/
+                                           {toPackageMetadata("system.package.A",
+                                                              ApplicationCategoryType::MEDIA),
+                                            toPackageMetadata("system.package.B",
+                                                              ApplicationCategoryType::MAPS)},
+                                           ioConfig);
+    TemporaryFile temporaryFile;
+    ASSERT_NE(temporaryFile.fd, -1);
+
+    ASSERT_RESULT_OK(OveruseConfigurationXmlHelper::writeXmlFile(expected, temporaryFile.path));
+
+    ALOGW("Wrote to file: %s", temporaryFile.path);
+
+    auto actual = OveruseConfigurationXmlHelper::parseXmlFile(temporaryFile.path);
+
+    ASSERT_RESULT_OK(actual);
+
+    EXPECT_THAT(*actual, ResourceOveruseConfigurationMatcher(expected))
+            << "Expected: " << expected.toString() << "\nActual: " << actual->toString();
+
+    temporaryFile.release();
+}
+
+TEST(OveruseConfigurationXmlHelperTest, TestWriteXmlFileWithVendorConfiguration) {
+    auto ioConfig = constructIoOveruseConfig(
+            /*componentLevel=*/toPerStateIoOveruseThreshold(ComponentType::VENDOR,
+                                                            1024 * kOneMegaByte, 512 * kOneMegaByte,
+                                                            3072 * kOneMegaByte),
+            /*packageSpecific=*/
+            {toPerStateIoOveruseThreshold("com.vendor.package.C", 400 * kOneMegaByte,
+                                          100 * kOneMegaByte, 200 * kOneMegaByte),
+             toPerStateIoOveruseThreshold("com.vendor.package.D", 1024 * kOneMegaByte,
+                                          500 * kOneMegaByte, 2048 * kOneMegaByte)},
+            /*categorySpecific=*/
+            {toPerStateIoOveruseThreshold("MAPS", 800 * kOneMegaByte, 900 * kOneMegaByte,
+                                          2048 * kOneMegaByte),
+             toPerStateIoOveruseThreshold("MEDIA", 600 * kOneMegaByte, 700 * kOneMegaByte,
+                                          1024 * kOneMegaByte)},
+            /*systemWide=*/{});
+    ResourceOveruseConfiguration expected =
+            constructResourceOveruseConfig(ComponentType::VENDOR,
+                                           /*safeToKill=*/
+                                           {"com.vendor.package.A", "com.vendor.package.B"},
+                                           /*vendorPrefixes=*/{"com.vendor.package"},
+                                           /*packageMetadata=*/
+                                           {toPackageMetadata("com.vendor.package.A",
+                                                              ApplicationCategoryType::MEDIA),
+                                            toPackageMetadata("com.vendor.package.B",
+                                                              ApplicationCategoryType::MAPS),
+                                            toPackageMetadata("com.third.party.package.C",
+                                                              ApplicationCategoryType::MEDIA),
+                                            toPackageMetadata("system.package.D",
+                                                              ApplicationCategoryType::MAPS)},
+                                           ioConfig);
+    TemporaryFile temporaryFile;
+    ASSERT_NE(temporaryFile.fd, -1);
+
+    ASSERT_RESULT_OK(OveruseConfigurationXmlHelper::writeXmlFile(expected, temporaryFile.path));
+
+    ALOGW("Wrote to file: %s", temporaryFile.path);
+
+    auto actual = OveruseConfigurationXmlHelper::parseXmlFile(temporaryFile.path);
+
+    ASSERT_RESULT_OK(actual);
+
+    EXPECT_THAT(*actual, ResourceOveruseConfigurationMatcher(expected))
+            << "Expected: " << expected.toString() << "\nActual: " << actual->toString();
+
+    temporaryFile.release();
+}
+
+TEST(OveruseConfigurationXmlHelperTest, TestWriteXmlFileWithThirdPartyConfiguration) {
+    auto ioConfig = constructIoOveruseConfig(
+            /*componentLevel=*/toPerStateIoOveruseThreshold(ComponentType::THIRD_PARTY,
+                                                            300 * kOneMegaByte, 150 * kOneMegaByte,
+                                                            500 * kOneMegaByte),
+            /*packageSpecific=*/{},
+            /*categorySpecific=*/{},
+            /*systemWide=*/{});
+    ResourceOveruseConfiguration expected =
+            constructResourceOveruseConfig(ComponentType::THIRD_PARTY,
+                                           /*safeToKill=*/{},
+                                           /*vendorPrefixes=*/{},
+                                           /*packageMetadata=*/{}, ioConfig);
+    TemporaryFile temporaryFile;
+    ASSERT_NE(temporaryFile.fd, -1);
+
+    ASSERT_RESULT_OK(OveruseConfigurationXmlHelper::writeXmlFile(expected, temporaryFile.path));
+
+    ALOGW("Wrote to file: %s", temporaryFile.path);
+
+    auto actual = OveruseConfigurationXmlHelper::parseXmlFile(temporaryFile.path);
+
+    ASSERT_RESULT_OK(actual);
+
+    EXPECT_THAT(*actual, ResourceOveruseConfigurationMatcher(expected))
+            << "Expected: " << expected.toString() << "\nActual: " << actual->toString();
+
+    temporaryFile.release();
+}
+
+TEST(OveruseConfigurationXmlHelperTest, TestFailsWriteXmlFileWithInvalidConfig) {
+    ResourceOveruseConfiguration resourceOveruseConfig;
+    resourceOveruseConfig.componentType = ComponentType::THIRD_PARTY;
+
+    TemporaryFile temporaryFile;
+    ASSERT_NE(temporaryFile.fd, -1);
+
+    ASSERT_FALSE(
+            OveruseConfigurationXmlHelper::writeXmlFile(resourceOveruseConfig, temporaryFile.path)
+                    .ok())
+            << "Should fail to write invalid config";
+
+    temporaryFile.release();
+}
+
 }  // namespace watchdog
 }  // namespace automotive
 }  // namespace android
diff --git a/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java b/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java
index 2520d60..066af56 100644
--- a/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java
+++ b/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java
@@ -16,7 +16,7 @@
 
 package com.android.car.developeroptions;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +30,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -51,30 +52,50 @@
     public void testAvailableIntentActivities_onlyAllowlisted() {
         List<ResolveInfo> results = mPm.queryIntentActivities(createSettingsPackageIntent(),
                 PackageManager.MATCH_ALL);
+        List<String> nonMatchingItems = new ArrayList<>();
         for (ResolveInfo resolved : results) {
-            assertThat(ACTIVITY_ALLOWLIST).contains(resolved.getComponentInfo().name);
+            if (!ACTIVITY_ALLOWLIST.contains(resolved.getComponentInfo().name)) {
+                nonMatchingItems.add(resolved.getComponentInfo().name);
+            }
         }
+        assertWithMessage("Unexpected activities found: " + nonMatchingItems.toString())
+                .that(nonMatchingItems.size()).isEqualTo(0);
     }
 
     @Test
     public void testAvailableIntentServices_returnsZero() {
         List<ResolveInfo> results = mPm.queryIntentServices(createSettingsPackageIntent(),
                 PackageManager.MATCH_ALL);
-        assertThat(results.size()).isEqualTo(0);
+        List<String> nonMatchingItems = new ArrayList<>();
+        for (ResolveInfo resolved : results) {
+            nonMatchingItems.add(resolved.getComponentInfo().name);
+        }
+        assertWithMessage("Unexpected services found: " + nonMatchingItems.toString())
+                .that(nonMatchingItems.size()).isEqualTo(0);
     }
 
     @Test
     public void testAvailableBroadcastReceivers_returnsZero() {
         List<ResolveInfo> results = mPm.queryBroadcastReceivers(createSettingsPackageIntent(),
                 PackageManager.MATCH_ALL);
-        assertThat(results.size()).isEqualTo(0);
+        List<String> nonMatchingItems = new ArrayList<>();
+        for (ResolveInfo resolved : results) {
+            nonMatchingItems.add(resolved.getComponentInfo().name);
+        }
+        assertWithMessage("Unexpected broadcast receivers found: " + nonMatchingItems.toString())
+                .that(nonMatchingItems.size()).isEqualTo(0);
     }
 
     @Test
     public void testAvailableContentProviders_returnsZero() {
         List<ResolveInfo> results = mPm.queryIntentContentProviders(createSettingsPackageIntent(),
                 PackageManager.MATCH_ALL);
-        assertThat(results.size()).isEqualTo(0);
+        List<String> nonMatchingItems = new ArrayList<>();
+        for (ResolveInfo resolved : results) {
+            nonMatchingItems.add(resolved.getComponentInfo().name);
+        }
+        assertWithMessage("Unexpected content providers found: " + nonMatchingItems.toString())
+                .that(nonMatchingItems.size()).isEqualTo(0);
     }
 
     private Intent createSettingsPackageIntent() {
diff --git a/service/Android.bp b/service/Android.bp
index 1bc18eb..7d24ac6 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -106,6 +106,10 @@
         "src/com/android/car/Utils.java",
     ],
 
+    static_libs: [
+        "com.android.car.internal.common",
+    ],
+
     product_variables: {
             pdk: {
                 enabled: false,
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 8f90480..d3724dd 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -859,10 +859,10 @@
          android:description="@string/car_permission_desc_set_car_vendor_category_10"/>
 
     <!-- Allows an application to receive Car input events.
-         <p>Protection level: signature
+         <p>Protection level: signature|privileged
     -->
     <permission android:name="android.car.permission.CAR_MONITOR_INPUT"
-                android:protectionLevel="signature"
+                android:protectionLevel="signature|privileged"
                 android:label="@string/car_permission_label_monitor_input"
                 android:description="@string/car_permission_desc_monitor_input"/>
 
diff --git a/service/jni/evs/EvsServiceContext.cpp b/service/jni/evs/EvsServiceContext.cpp
index f338eef..b0cc129 100644
--- a/service/jni/evs/EvsServiceContext.cpp
+++ b/service/jni/evs/EvsServiceContext.cpp
@@ -119,8 +119,13 @@
     }
 
     if (isCameraOpened()) {
-        LOG(DEBUG) << "Camera " << id << " is has opened already.";
-        return true;
+        if (!strcmp(id, mCameraIdInUse)) {
+            LOG(DEBUG) << "Camera " << id << " is has opened already.";
+            return true;
+        } else {
+            // Close a current camera device.
+            mService->closeCamera(mCamera);
+        }
     }
 
     sp<IEvsCamera> camera = IEvsCamera::castFrom(mService->openCamera(id));
@@ -140,6 +145,7 @@
         std::lock_guard<std::mutex> lock(mLock);
         mCamera = camera;
         mStreamHandler = streamHandler;
+        mCameraIdInUse = id;
     }
 
     return true;
diff --git a/service/jni/evs/EvsServiceContext.h b/service/jni/evs/EvsServiceContext.h
index d96eadb..84c0012 100644
--- a/service/jni/evs/EvsServiceContext.h
+++ b/service/jni/evs/EvsServiceContext.h
@@ -169,6 +169,9 @@
     // Bookkeeps descriptors of received frame buffers.
     std::map<int, hardware::automotive::evs::V1_1::BufferDesc> mBufferRecords GUARDED_BY(mLock);
 
+    // A name of the camera device currently in use.
+    const char* mCameraIdInUse;
+
     // Service name for EVS enumerator
     static const char* kServiceName;
 
diff --git a/service/res/values-de/strings.xml b/service/res/values-de/strings.xml
index 490e7c8..156265d 100644
--- a/service/res/values-de/strings.xml
+++ b/service/res/values-de/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="car_permission_label" msgid="2215078736675564541">"Fahrzeuginformationen"</string>
+    <string name="car_permission_label" msgid="2215078736675564541">"Fahrzeug­informationen"</string>
     <string name="car_permission_desc" msgid="3584369074931334964">"Zugriff auf Informationen deines Autos"</string>
     <string name="car_permission_label_camera" msgid="3725702064841827180">"auf die Autokamera zuzugreifen"</string>
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Auf Autokamera(s) zugreifen."</string>
diff --git a/service/res/values-es/strings.xml b/service/res/values-es/strings.xml
index 003d498..12ee40f 100644
--- a/service/res/values-es/strings.xml
+++ b/service/res/values-es/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="car_permission_label" msgid="2215078736675564541">"Información sobre el coche"</string>
+    <string name="car_permission_label" msgid="2215078736675564541">"información sobre el coche"</string>
     <string name="car_permission_desc" msgid="3584369074931334964">"acceder a los datos de tu coche"</string>
     <string name="car_permission_label_camera" msgid="3725702064841827180">"acceder a la cámara del coche"</string>
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Acceder a las cámaras del coche."</string>
diff --git a/service/res/values-it/strings.xml b/service/res/values-it/strings.xml
index 05b2c23..566a98a 100644
--- a/service/res/values-it/strings.xml
+++ b/service/res/values-it/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="car_permission_label" msgid="2215078736675564541">"Informazioni sull\'auto"</string>
-    <string name="car_permission_desc" msgid="3584369074931334964">"Accedi alle informazioni della tua auto"</string>
+    <string name="car_permission_desc" msgid="3584369074931334964">"accedere alle informazioni della tua auto"</string>
     <string name="car_permission_label_camera" msgid="3725702064841827180">"Accesso alla videocamera dell\'automobile"</string>
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Consente di accedere alle videocamere dell\'automobile."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"Accesso alle informazioni sulla carica dell\'automobile"</string>
diff --git a/service/res/values-ky/strings.xml b/service/res/values-ky/strings.xml
index ede73eb..04afe22 100644
--- a/service/res/values-ky/strings.xml
+++ b/service/res/values-ky/strings.xml
@@ -100,7 +100,7 @@
     <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"EVS\'ти алдын ала көрүү аракетин сурануу"</string>
     <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"Тутумдан EVS\'ти алдын ала көрүү аракетин иштетүүнү сурануу"</string>
     <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"EVS\'ти алдын ала көрүү аракетин көзөмөлдөө"</string>
-    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"Тутумдун EVS\'ти алдын ала көрүү аракетин көзөмөлдөө"</string>
+    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"Системанын EVS\'ти алдын ала көрүү аракетин көзөмөлдөө"</string>
     <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"EVS камерасын иштетүү"</string>
     <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"EVS камерасынын агымдарына жазылуу"</string>
     <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"EVS кызматынын статусун көзөмөлдөө"</string>
@@ -174,7 +174,7 @@
     <string name="importance_default" msgid="8587741629268312938">"Демейки маанилүүлүк"</string>
     <string name="importance_high" msgid="3141530792377745041">"Өтө маанилүү"</string>
     <string name="factory_reset_notification_title" msgid="2530056626309489398">"Баштапкы абалга кайтаруу талап кылынат"</string>
-    <string name="factory_reset_notification_text" msgid="6517642677900094724">"Инфозоок тутумундагы бардык маалымат өчүрүлөт. Баштапкы абалга келтирилгенден кийин жаңы профилди жөндөп алсаңыз болот."</string>
+    <string name="factory_reset_notification_text" msgid="6517642677900094724">"Инфозоок тутумундагы бардык нерселер өчүрүлөт. Баштапкы абалга келтирилгенден кийин жаңы профилди жөндөп алсаңыз болот."</string>
     <string name="factory_reset_notification_button" msgid="5450535366202106371">"Дагы"</string>
     <string name="factory_reset_parked_title" msgid="258340498079453871">"Инфозоок тутумун баштапкы абалга келтирүү"</string>
     <string name="factory_reset_parked_text" msgid="910347526834275166">"Тутум баштапкы абалга кайтаруу жана бардык маалыматты өчүрүү сурамын алды. Аны азыр аткарсаңыз болот же унаа кийинки жолу от алганда тутум башкапкы абалга кайтарылат. Андан кийин жаңы профилди жөндөп алсаңыз болот."</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index e5d37d3..f0c611c 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -404,7 +404,11 @@
     <integer name="config_maxSuspendWaitDuration">180000</integer>
 
     <!-- A name of a camera device that provides the rearview through EVS service -->
-    <string name="config_evsRearviewCameraId">/dev/video10</string>
+    <string name="config_evsRearviewCameraId" translatable="false">/dev/video10</string>
+
+    <!-- The camera Activity name for EVS, if defined, the Activity will be launched by
+         CarEvsService. -->
+    <string name="config_evsCameraActivity" translatable="false"></string>
 
     <!-- A configuration flag to adjust Wifi for suspend. -->
     <bool name="config_wifiAdjustmentForSuspend">false</bool>
diff --git a/service/src/com/android/car/CanBusErrorNotifier.java b/service/src/com/android/car/CanBusErrorNotifier.java
deleted file mode 100644
index e4770af..0000000
--- a/service/src/com/android/car/CanBusErrorNotifier.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Class used to notify user about CAN bus failure.
- */
-final class CanBusErrorNotifier {
-    private static final String TAG = CarLog.tagFor(CanBusErrorNotifier.class);
-    private static final int NOTIFICATION_ID = 1;
-    private static final boolean IS_RELEASE_BUILD = "user".equals(Build.TYPE);
-
-    private final Context mContext;
-    private final NotificationManager mNotificationManager;
-
-    // Contains a set of objects that reported failure. The notification will be hidden only when
-    // this set is empty (all reported objects are in love and peace with the vehicle).
-    @GuardedBy("this")
-    private final Set<Object> mReportedObjects = new HashSet<>();
-
-    CanBusErrorNotifier(Context context) {
-        mNotificationManager = (NotificationManager) context.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        mContext = context;
-    }
-
-    public void removeFailureReport(Object sender) {
-        setCanBusFailure(false, sender);
-    }
-
-    public void reportFailure(Object sender) {
-        setCanBusFailure(true, sender);
-    }
-
-    private void setCanBusFailure(boolean failed, Object sender) {
-        boolean shouldShowNotification;
-        synchronized (this) {
-            boolean changed = failed
-                    ? mReportedObjects.add(sender) : mReportedObjects.remove(sender);
-
-            if (!changed) {
-                return;
-            }
-
-            shouldShowNotification = !mReportedObjects.isEmpty();
-        }
-
-        if (Log.isLoggable(TAG, Log.INFO)) {
-            Slog.i(TAG, "Changing CAN bus failure state to " + shouldShowNotification);
-        }
-
-        if (shouldShowNotification) {
-            showNotification();
-        } else {
-            hideNotification();
-        }
-    }
-
-    private void showNotification() {
-        if (IS_RELEASE_BUILD) {
-            // TODO: for user, we should show message to take car to the dealer. bug:32096297
-            return;
-        }
-        Notification notification =
-                new Notification.Builder(mContext, NotificationChannel.DEFAULT_CHANNEL_ID)
-                        .setContentTitle(mContext.getString(R.string.car_can_bus_failure))
-                        .setContentText(mContext.getString(R.string.car_can_bus_failure_desc))
-                        .setSmallIcon(R.drawable.car_ic_error)
-                        .setOngoing(true)
-                        .build();
-        mNotificationManager.notify(TAG, NOTIFICATION_ID, notification);
-    }
-
-    private void hideNotification() {
-        if (IS_RELEASE_BUILD) {
-            // TODO: for user, we should show message to take car to the dealer. bug:32096297
-            return;
-        }
-        mNotificationManager.cancel(TAG, NOTIFICATION_ID);
-    }
-}
diff --git a/service/src/com/android/car/CarBugreportManagerService.java b/service/src/com/android/car/CarBugreportManagerService.java
index 2be403d..67eec21 100644
--- a/service/src/com/android/car/CarBugreportManagerService.java
+++ b/service/src/com/android/car/CarBugreportManagerService.java
@@ -42,6 +42,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.BufferedReader;
 import java.io.DataInputStream;
@@ -86,6 +87,7 @@
     private static final int SOCKET_CONNECTION_RETRY_DELAY_IN_MS = 5000;
 
     private final Context mContext;
+    private final boolean mIsUserBuild;
     private final Object mLock = new Object();
 
     private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
@@ -100,7 +102,14 @@
      * @param context the context
      */
     public CarBugreportManagerService(Context context) {
+        // Per https://source.android.com/setup/develop/new-device, user builds are debuggable=0
+        this(context, !Build.IS_DEBUGGABLE);
+    }
+
+    @VisibleForTesting
+    CarBugreportManagerService(Context context, boolean isUserBuild) {
         mContext = context;
+        mIsUserBuild = isUserBuild;
     }
 
     @Override
@@ -186,8 +195,7 @@
 
     /** Checks only on user builds. */
     private void ensureTheCallerIsDesignatedBugReportApp() {
-        if (Build.IS_DEBUGGABLE) {
-            // Per https://source.android.com/setup/develop/new-device, user builds are debuggable=0
+        if (!mIsUserBuild) {
             return;
         }
         String defaultAppPkgName = mContext.getString(R.string.config_car_bugreport_application);
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index 23644a3..d629b50 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -16,6 +16,10 @@
 
 package com.android.car;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
 /**
  * Helper class for class tags for CarService.
  */
@@ -49,4 +53,10 @@
         if (tag.matches(MATCHER)) return tag;
         return PREFIX + tag;
     }
+
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
+            details = "private constructor")
+    private CarLog() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
 }
diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java
index 6a90f9c..7aa2cca 100644
--- a/service/src/com/android/car/CarService.java
+++ b/service/src/com/android/car/CarService.java
@@ -54,28 +54,11 @@
 
     private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
 
-    private CanBusErrorNotifier mCanBusErrorNotifier;
     private ICarImpl mICarImpl;
     private IVehicle mVehicle;
 
     private String mVehicleInterfaceName;
 
-    // If 10 crashes of Vehicle HAL occurred within 10 minutes then thrown an exception in
-    // Car Service.
-    private final CrashTracker mVhalCrashTracker = new CrashTracker(
-            10,  // Max crash count.
-            10 * 60 * 1000,  // 10 minutes - sliding time window.
-            () -> {
-                if (IS_USER_BUILD) {
-                    Slog.e(CarLog.TAG_SERVICE, "Vehicle HAL keeps crashing, notifying user...");
-                    mCanBusErrorNotifier.reportFailure(CarService.this);
-                } else {
-                    throw new RuntimeException(
-                            "Vehicle HAL crashed too many times in a given time frame");
-                }
-            }
-    );
-
     private final VehicleDeathRecipient mVehicleDeathRecipient = new VehicleDeathRecipient();
 
     @Override
@@ -84,8 +67,6 @@
                 Trace.TRACE_TAG_SYSTEM_SERVER, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
         initTiming.traceBegin("CarService.onCreate");
 
-        mCanBusErrorNotifier = new CanBusErrorNotifier(this /* context */);
-
         initTiming.traceBegin("getVehicle");
         mVehicle = getVehicle();
         initTiming.traceEnd();
@@ -107,7 +88,6 @@
         mICarImpl = new ICarImpl(this,
                 mVehicle,
                 SystemInterface.Builder.defaultSystemInterface(this).build(),
-                mCanBusErrorNotifier,
                 mVehicleInterfaceName);
         mICarImpl.init();
 
@@ -129,7 +109,6 @@
         EventLog.writeEvent(EventLogTags.CAR_SERVICE_CREATE, mVehicle == null ? 0 : 1);
         Slog.i(CarLog.TAG_SERVICE, "Service onDestroy");
         mICarImpl.release();
-        mCanBusErrorNotifier.removeFailureReport(this);
 
         if (mVehicle != null) {
             try {
@@ -176,10 +155,6 @@
             vehicle = getVehicle();
         }
 
-        if (vehicle != null) {
-            mCanBusErrorNotifier.removeFailureReport(this);
-        }
-
         return vehicle;
     }
 
@@ -202,34 +177,8 @@
         @Override
         public void serviceDied(long cookie) {
             EventLog.writeEvent(EventLogTags.CAR_SERVICE_VHAL_DIED, cookie);
-            if (RESTART_CAR_SERVICE_WHEN_VHAL_CRASH) {
-                Slog.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died. Car service will restart***");
-                Process.killProcess(Process.myPid());
-                return;
-            }
-
-            Slog.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died.***");
-
-            try {
-                mVehicle.unlinkToDeath(this);
-            } catch (RemoteException e) {
-                Slog.e(CarLog.TAG_SERVICE, "Failed to unlinkToDeath", e); // Log and continue.
-            }
-            mVehicle = null;
-
-            mVhalCrashTracker.crashDetected();
-
-            Slog.i(CarLog.TAG_SERVICE,
-                    "Trying to reconnect to Vehicle HAL: " + mVehicleInterfaceName);
-            mVehicle = getVehicleWithTimeout(WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS);
-            if (mVehicle == null) {
-                throw new IllegalStateException("Failed to reconnect to Vehicle HAL");
-            }
-
-            linkToDeath(mVehicle, this);
-
-            Slog.i(CarLog.TAG_SERVICE, "Notifying car service Vehicle HAL reconnected...");
-            mICarImpl.vehicleHalReconnected(mVehicle);
+            Slog.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died. Car service will restart***");
+            Process.killProcess(Process.myPid());
         }
     }
 
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index dcc9371..1df0b63 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -90,6 +90,7 @@
 
 import com.android.car.am.FixedActivityService;
 import com.android.car.audio.CarAudioService;
+import com.android.car.evs.CarEvsService;
 import com.android.car.garagemode.GarageModeService;
 import com.android.car.hal.InputHalService;
 import com.android.car.hal.UserHalService;
@@ -179,6 +180,9 @@
     private static final String DRIVING_STATE_PARK = "park";
     private static final String DRIVING_STATE_REVERSE = "reverse";
 
+    private static final String COMMAND_SET_REARVIEW_CAMERA_ID = "set-rearview-camera-id";
+    private static final String COMMAND_GET_REARVIEW_CAMERA_ID = "get-rearview-camera-id";
+
     private static final String[] CREATE_OR_MANAGE_USERS_PERMISSIONS = new String[] {
             android.Manifest.permission.CREATE_USERS,
             android.Manifest.permission.MANAGE_USERS
@@ -336,6 +340,7 @@
     private final GarageModeService mGarageModeService;
     private final CarUserService mCarUserService;
     private final CarOccupantZoneService mCarOccupantZoneService;
+    private final CarEvsService mCarEvsService;
     private long mKeyDownTime;
 
     CarShellCommand(Context context,
@@ -351,7 +356,8 @@
             SystemInterface systemInterface,
             GarageModeService garageModeService,
             CarUserService carUserService,
-            CarOccupantZoneService carOccupantZoneService) {
+            CarOccupantZoneService carOccupantZoneService,
+            CarEvsService carEvsService) {
         mContext = context;
         mHal = hal;
         mCarAudioService = carAudioService;
@@ -366,6 +372,7 @@
         mGarageModeService = garageModeService;
         mCarUserService = carUserService;
         mCarOccupantZoneService = carOccupantZoneService;
+        mCarEvsService = carEvsService;
     }
 
     @Override
@@ -566,6 +573,15 @@
         pw.printf("\t%s [%s] [%s]\n", COMMAND_POWER_OFF, POWER_OFF_SKIP_GARAGEMODE,
                 POWER_OFF_SHUTDOWN);
         pw.println("\t  Powers off the car.");
+
+        pw.printf("\t%s <CAMERA_ID>\n", COMMAND_SET_REARVIEW_CAMERA_ID);
+        pw.println("\t  Configures a target camera device CarEvsService to use.");
+        pw.println("\t  If CAMEAR_ID is \"default\", this command will configure CarEvsService ");
+        pw.println("\t  to use its default camera device.");
+
+        pw.printf("\t%s\n", COMMAND_GET_REARVIEW_CAMERA_ID);
+        pw.println("\t  Gets the name of the camera device CarEvsService is using for " +
+                "the rearview.");
     }
 
     private static int showInvalidArguments(IndentingPrintWriter pw) {
@@ -882,6 +898,12 @@
             case COMMAND_POWER_OFF:
                 powerOff(args, writer);
                 break;
+            case COMMAND_SET_REARVIEW_CAMERA_ID:
+                setRearviewCameraId(args, writer);
+                break;
+            case COMMAND_GET_REARVIEW_CAMERA_ID:
+                getRearviewCameraId(writer);
+                break;
 
             default:
                 writer.println("Unknown command: \"" + cmd + "\"");
@@ -2029,6 +2051,25 @@
 
     }
 
+    // Set a target camera device for the rearview
+    private void setRearviewCameraId(String[] args, IndentingPrintWriter writer) {
+        if (args.length != 2) {
+            showInvalidArguments(writer);
+            return;
+        }
+
+        if (!mCarEvsService.setRearviewCameraIdFromCommand(args[1])) {
+            writer.println("Failed to set CarEvsService rearview camera device id.");
+        } else {
+            writer.printf("CarEvsService is set to use %s.\n", args[1]);
+        }
+    }
+
+    private void getRearviewCameraId(IndentingPrintWriter writer) {
+        writer.printf("CarEvsService is using %s for the rearview.\n",
+                mCarEvsService.getRearviewCameraIdFromCommand());
+    }
+
     // Check if the given property is global
     private static boolean isPropertyAreaTypeGlobal(@Nullable String property) {
         if (property == null) {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 7c6433a..68af541 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -18,6 +18,8 @@
 
 import static com.android.car.CarService.CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS;
 import static com.android.car.CarService.CAR_SERVICE_INIT_TIMING_TAG;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT;
 
 import android.annotation.MainThread;
@@ -60,6 +62,7 @@
 import com.android.car.evs.CarEvsService;
 import com.android.car.garagemode.GarageModeService;
 import com.android.car.hal.VehicleHal;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.internal.ICarServiceHelper;
 import com.android.car.internal.ICarSystemServerClient;
 import com.android.car.internal.common.EventLogTags;
@@ -156,15 +159,15 @@
     private final ICarSystemServerClientImpl mICarSystemServerClientImpl;
 
     public ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
-            CanBusErrorNotifier errorNotifier, String vehicleInterfaceName) {
-        this(serviceContext, vehicle, systemInterface, errorNotifier, vehicleInterfaceName,
+            String vehicleInterfaceName) {
+        this(serviceContext, vehicle, systemInterface, vehicleInterfaceName,
                 /* carUserService= */ null, /* carWatchdogService= */ null,
                 /* powerPolicyDaemon= */ null);
     }
 
     @VisibleForTesting
     ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
-            CanBusErrorNotifier errorNotifier, String vehicleInterfaceName,
+            String vehicleInterfaceName,
             @Nullable CarUserService carUserService,
             @Nullable CarWatchdogService carWatchdogService,
             @Nullable ICarPowerPolicySystemNotification powerPolicyDaemon) {
@@ -642,23 +645,11 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE)
     public int getCarConnectionType() {
         return Car.CONNECTION_TYPE_EMBEDDED;
     }
 
-    public CarServiceBase getCarInternalService(String serviceName) {
-        switch (serviceName) {
-            case INTERNAL_INPUT_SERVICE:
-                return mCarInputService;
-            case INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE:
-                return mSystemActivityMonitoringService;
-            default:
-                Slog.w(CarLog.TAG_SERVICE, "getCarInternalService for unknown service:"
-                        + serviceName);
-                return null;
-        }
-    }
-
     public static void assertVehicleHalMockPermission(Context context) {
         assertPermission(context, Car.PERMISSION_MOCK_VEHICLE_HAL);
     }
@@ -739,6 +730,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -753,6 +745,7 @@
         }
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     private void dumpIndenting(FileDescriptor fd, IndentingPrintWriter writer, String[] args) {
         if (args == null || args.length == 0 || (args.length > 0 && "-a".equals(args[0]))) {
             writer.println("*Dump car service*");
@@ -797,6 +790,7 @@
         }
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     private void dumpAllHals(IndentingPrintWriter writer) {
         writer.println("*Dump Vehicle HAL*");
         writer.println("Vehicle HAL Interface: " + mVehicleInterfaceName);
@@ -809,6 +803,7 @@
         }
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     private void showDumpHelp(IndentingPrintWriter writer) {
         writer.println("Car service dump usage:");
         writer.println("[NO ARG]");
@@ -849,15 +844,17 @@
         return new CarShellCommand(mContext, mHal, mCarAudioService, mCarPackageManagerService,
                 mCarProjectionService, mCarPowerManagementService, mFixedActivityService,
                 mFeatureController, mCarInputService, mCarNightService, mSystemInterface,
-                mGarageModeService, mCarUserService, mCarOccupantZoneService);
+                mGarageModeService, mCarUserService, mCarOccupantZoneService, mCarEvsService);
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     private void dumpListOfServices(IndentingPrintWriter writer) {
         for (CarServiceBase service : mAllServices) {
             writer.println(service.getClass().getName());
         }
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     private void dumpAllServices(IndentingPrintWriter writer) {
         writer.println("*Dump all services*");
         for (CarServiceBase service : mAllServices) {
@@ -868,6 +865,7 @@
         }
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     private void dumpIndividualServices(IndentingPrintWriter writer, String... serviceNames) {
         for (String serviceName : serviceNames) {
             writer.printf("** Dumping %s\n\n", serviceName);
@@ -888,6 +886,7 @@
                 .findFirst().orElse(null);
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     private void dumpService(CarServiceBase service, IndentingPrintWriter writer) {
         try {
             service.dump(writer);
diff --git a/service/src/com/android/car/PerUserCarService.java b/service/src/com/android/car/PerUserCarService.java
index ee4dafe..6a9291a 100644
--- a/service/src/com/android/car/PerUserCarService.java
+++ b/service/src/com/android/car/PerUserCarService.java
@@ -76,7 +76,7 @@
 
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
             mPerUserCarDevicePolicyService = PerUserCarDevicePolicyService.getInstance(context);
-            mPerUserCarDevicePolicyService.registerBroadcastReceiver();
+            mPerUserCarDevicePolicyService.onCreate();
         } else if (DBG) {
             Slogf.d(TAG, "Not setting PerUserCarDevicePolicyService because device doesn't have %s",
                     PackageManager.FEATURE_DEVICE_ADMIN);
diff --git a/service/src/com/android/car/SparseArrayStream.java b/service/src/com/android/car/SparseArrayStream.java
index cbe2ca3..574f107 100644
--- a/service/src/com/android/car/SparseArrayStream.java
+++ b/service/src/com/android/car/SparseArrayStream.java
@@ -15,9 +15,13 @@
  */
 package com.android.car;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.util.Pair;
 import android.util.SparseArray;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
@@ -37,4 +41,10 @@
         return IntStream.range(0, array.size()).mapToObj(
             i -> new Pair<>(array.keyAt(i), array.valueAt(i)));
     }
+
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
+            details = "private constructor")
+    private SparseArrayStream() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
 }
diff --git a/service/src/com/android/car/Utils.java b/service/src/com/android/car/Utils.java
index 41ab67b..c6f5ef9 100644
--- a/service/src/com/android/car/Utils.java
+++ b/service/src/com/android/car/Utils.java
@@ -15,6 +15,8 @@
  */
 package com.android.car;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -23,6 +25,8 @@
 import android.bluetooth.BluetoothProfile;
 import android.util.SparseArray;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -140,7 +144,7 @@
      * A specific service in CarService can choose to use a circular buffer of N records to keep
      * track of the last N transitions.
      */
-    public static class TransitionLog {
+    public static final class TransitionLog {
         private String mServiceName; // name of the service or tag
         private Object mFromState; // old state
         private Object mToState; // new state
@@ -166,8 +170,9 @@
 
         @Override
         public String toString() {
-            return timeToLog(mTimestampMs) + " " + mServiceName + ": " + (mExtra != null ? mExtra
-                    : "") + " changed from " + mFromState + " to " + mToState;
+            return timeToLog(mTimestampMs) + " " + mServiceName + ": "
+                    + (mExtra != null ? mExtra + " " : "")
+                    + "changed from " + mFromState + " to " + mToState;
         }
     }
 
@@ -278,4 +283,9 @@
         return outputStream.toByteArray();
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
+            details = "private constructor")
+    private Utils() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
 }
diff --git a/service/src/com/android/car/admin/NewUserDisclaimerActivity.java b/service/src/com/android/car/admin/NewUserDisclaimerActivity.java
index 4101c34..8a186a4 100644
--- a/service/src/com/android/car/admin/NewUserDisclaimerActivity.java
+++ b/service/src/com/android/car/admin/NewUserDisclaimerActivity.java
@@ -30,6 +30,7 @@
 import com.android.car.CarLog;
 import com.android.car.R;
 import com.android.car.admin.ui.ManagedDeviceTextView;
+import com.android.internal.annotations.VisibleForTesting;
 
 // TODO(b/171603586): STOPSHIP move UI related activities to CarSettings
 /**
@@ -65,6 +66,11 @@
         // and/or integrate it with UserNoticeService
     }
 
+    @VisibleForTesting
+    Button getAcceptButton() {
+        return mAcceptButton;
+    }
+
     private void accept() {
         if (DEBUG) Slog.d(TAG, "user accepted");
 
@@ -72,13 +78,8 @@
         finish();
     }
 
-    private static Intent newIntent(Context context) {
-        return new Intent(context, NewUserDisclaimerActivity.class);
-    }
-
     static void showNotification(Context context) {
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, NOTIFICATION_ID,
-                newIntent(context), PendingIntent.FLAG_IMMUTABLE, null);
+        PendingIntent pendingIntent = getPendingIntent(context, /* extraFlags= */ 0);
 
         Notification notification = NotificationHelper
                 .newNotificationBuilder(context, NotificationManager.IMPORTANCE_DEFAULT)
@@ -104,7 +105,13 @@
                     + context.getUserId());
         }
         context.getSystemService(NotificationManager.class).cancel(NOTIFICATION_ID);
-        PendingIntent.getActivity(context, NOTIFICATION_ID, newIntent(context),
-                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT).cancel();
+        getPendingIntent(context, PendingIntent.FLAG_UPDATE_CURRENT).cancel();
+    }
+
+    @VisibleForTesting
+    static PendingIntent getPendingIntent(Context context, int extraFlags) {
+        return PendingIntent.getActivity(context, NOTIFICATION_ID,
+                new Intent(context, NewUserDisclaimerActivity.class),
+                PendingIntent.FLAG_IMMUTABLE | extraFlags);
     }
 }
diff --git a/service/src/com/android/car/admin/NotificationHelper.java b/service/src/com/android/car/admin/NotificationHelper.java
index 44bba11..217bb46 100644
--- a/service/src/com/android/car/admin/NotificationHelper.java
+++ b/service/src/com/android/car/admin/NotificationHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.car.admin;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -24,18 +26,26 @@
 import android.os.Bundle;
 
 import com.android.car.R;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.internal.annotations.VisibleForTesting;
 
-// TODO(b/171603586): STOPSHIP move this class to CarSettings
+import java.util.Objects;
+
+// TODO: move this class to CarSettings or at least to some common package (not admin)
+
 /**
  * Helper for notification-related tasks
  */
 public final class NotificationHelper {
 
     static final int FACTORY_RESET_NOTIFICATION_ID = 42;
-    public static final int NEW_USER_DISCLAIMER_NOTIFICATION_ID = 108;
 
-    static final String IMPORTANCE_DEFAULT_ID = "importance_default";
-    static final String IMPORTANCE_HIGH_ID = "importance_high";
+    static final int NEW_USER_DISCLAIMER_NOTIFICATION_ID = 108;
+
+    @VisibleForTesting
+    static final String CHANNEL_ID_DEFAULT = "channel_id_default";
+    @VisibleForTesting
+    static final String CHANNEL_ID_HIGH = "channel_id_high";
 
     /**
      * Creates a notification (and its notification channel) for the given importance type, setting
@@ -48,14 +58,16 @@
     @NonNull
     public static Notification.Builder newNotificationBuilder(Context context,
             @NotificationManager.Importance int importance) {
-        String importanceId, importanceName;
+        Objects.requireNonNull(context, "context cannot be null");
+
+        String channelId, importanceName;
         switch (importance) {
             case NotificationManager.IMPORTANCE_DEFAULT:
-                importanceId = IMPORTANCE_DEFAULT_ID;
+                channelId = CHANNEL_ID_DEFAULT;
                 importanceName = context.getString(R.string.importance_default);
                 break;
             case NotificationManager.IMPORTANCE_HIGH:
-                importanceId = IMPORTANCE_HIGH_ID;
+                channelId = CHANNEL_ID_HIGH;
                 importanceName = context.getString(R.string.importance_high);
                 break;
             default:
@@ -63,15 +75,17 @@
         }
         NotificationManager notificationMgr = context.getSystemService(NotificationManager.class);
         notificationMgr.createNotificationChannel(
-                new NotificationChannel(importanceId, importanceName, importance));
+                new NotificationChannel(channelId, importanceName, importance));
 
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 context.getString(com.android.internal.R.string.android_system_label));
 
-        return new Notification.Builder(context, importanceId).addExtras(extras);
+        return new Notification.Builder(context, channelId).addExtras(extras);
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
+            details = "private constructor")
     private NotificationHelper() {
         throw new UnsupportedOperationException("Contains only static methods");
     }
diff --git a/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java b/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java
index 4373a74..50ff4f7 100644
--- a/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java
+++ b/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java
@@ -16,6 +16,7 @@
 package com.android.car.admin;
 
 import static com.android.car.admin.CarDevicePolicyService.DEBUG;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.annotation.IntDef;
 import android.app.admin.DevicePolicyManager;
@@ -28,12 +29,14 @@
 import android.util.Slog;
 
 import com.android.car.CarLog;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
-// TODO(b/175057848) add unit tests
 /**
  * User-specific {@code CarDevicePolicyManagerService}.
  */
@@ -92,6 +95,8 @@
      * Gests the singleton instance, creating it if necessary.
      */
     public static PerUserCarDevicePolicyService getInstance(Context context) {
+        Objects.requireNonNull(context, "context cannot be null");
+
         synchronized (SLOCK) {
             if (sInstance == null) {
                 sInstance = new PerUserCarDevicePolicyService(context.getApplicationContext());
@@ -102,14 +107,15 @@
         }
     }
 
-    private PerUserCarDevicePolicyService(Context context) {
+    @VisibleForTesting
+    PerUserCarDevicePolicyService(Context context) {
         mContext = context;
     }
 
     /**
-     * Register a broadcast receiver to receive the proper events.
+     * Callback for when the service is created.
      */
-    public void registerBroadcastReceiver() {
+    public void onCreate() {
         if (DEBUG) Slog.d(TAG, "registering BroadcastReceiver");
 
         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(
@@ -130,6 +136,7 @@
     /**
      * Dump its contents.
      */
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter pw) {
         synchronized (SLOCK) {
             pw.printf("mNewUserDisclaimerStatus: %s\n",
@@ -166,7 +173,16 @@
         }
     }
 
-    private String newUserDisclaimerStatusToString(@NewUserDisclaimerStatus int status) {
+    @VisibleForTesting
+    @NewUserDisclaimerStatus
+    int getNewUserDisclaimerStatus() {
+        synchronized (SLOCK) {
+            return mNewUserDisclaimerStatus;
+        }
+    }
+
+    @VisibleForTesting
+    static String newUserDisclaimerStatusToString(@NewUserDisclaimerStatus int status) {
         return DebugUtils.constantToString(PerUserCarDevicePolicyService.class,
                 PREFIX_NEW_USER_DISCLAIMER_STATUS, status);
     }
diff --git a/service/src/com/android/car/audio/CarAudioContext.java b/service/src/com/android/car/audio/CarAudioContext.java
index ecd8259..be0974f 100644
--- a/service/src/com/android/car/audio/CarAudioContext.java
+++ b/service/src/com/android/car/audio/CarAudioContext.java
@@ -16,12 +16,15 @@
 
 package com.android.car.audio;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
 import android.annotation.IntDef;
 import android.media.AudioAttributes;
 import android.media.AudioAttributes.AttributeUsage;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -261,6 +264,7 @@
         return CarAudioContext.EMERGENCY == audioContext || CarAudioContext.SAFETY == audioContext;
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     static String toString(@AudioContext int audioContext) {
         String name = CONTEXT_NAMES.get(audioContext);
         if (name != null) {
diff --git a/service/src/com/android/car/audio/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
index 3fa72da..cba5661 100644
--- a/service/src/com/android/car/audio/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.audio;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.media.AudioDeviceInfo;
@@ -216,6 +217,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public String toString() {
         return "address: " + mAudioDeviceInfo.getAddress()
                 + " sampleRate: " + getSampleRate()
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index fe596bc6..9c40725 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.audio;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.annotation.NonNull;
@@ -188,6 +189,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
     public String toString() {
         return "CarVolumeGroup id: " + mId
                 + " currentGainIndex: " + mCurrentGainIndex
diff --git a/service/src/com/android/car/audio/FocusEntry.java b/service/src/com/android/car/audio/FocusEntry.java
index c2c1e98..78a53bc 100644
--- a/service/src/com/android/car/audio/FocusEntry.java
+++ b/service/src/com/android/car/audio/FocusEntry.java
@@ -106,13 +106,9 @@
                 == PackageManager.PERMISSION_GRANTED);
     }
 
-    String getUsageName() {
-        return mAudioFocusInfo.getAttributes().usageToString();
-    }
-
     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter writer) {
-        writer.printf("%s - %s\n", getClientId(), getUsageName());
+        writer.printf("%s - %s\n", getClientId(), mAudioFocusInfo.getAttributes().usageToString());
         writer.increaseIndent();
         // Prints in single line
         writer.printf("Receives Duck Events: %b, ", receivesDuckEvents());
diff --git a/service/src/com/android/car/cluster/ClusterHomeService.java b/service/src/com/android/car/cluster/ClusterHomeService.java
index 0baa793..b032d93 100644
--- a/service/src/com/android/car/cluster/ClusterHomeService.java
+++ b/service/src/com/android/car/cluster/ClusterHomeService.java
@@ -21,6 +21,7 @@
 import static com.android.car.hal.ClusterHalService.DISPLAY_OFF;
 import static com.android.car.hal.ClusterHalService.DISPLAY_ON;
 import static com.android.car.hal.ClusterHalService.DONT_CARE;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.app.ActivityOptions;
 import android.car.Car;
@@ -54,6 +55,7 @@
 import com.android.car.R;
 import com.android.car.am.FixedActivityService;
 import com.android.car.hal.ClusterHalService;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.server.utils.Slogf;
 
 /**
@@ -173,6 +175,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter writer) {
         // TODO: record the latest states from both sides
     }
diff --git a/service/src/com/android/car/cluster/ClusterNavigationService.java b/service/src/com/android/car/cluster/ClusterNavigationService.java
index 28e5a65..de6aa89 100644
--- a/service/src/com/android/car/cluster/ClusterNavigationService.java
+++ b/service/src/com/android/car/cluster/ClusterNavigationService.java
@@ -15,9 +15,10 @@
  */
 package com.android.car.cluster;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.car.Car;
 import android.car.CarAppFocusManager;
-import android.car.cluster.renderer.IInstrumentCluster;
 import android.car.cluster.renderer.IInstrumentClusterNavigation;
 import android.car.navigation.CarNavigationInstrumentCluster;
 import android.content.Context;
@@ -33,6 +34,7 @@
 import com.android.car.CarLog;
 import com.android.car.CarServiceBase;
 import com.android.car.ICarImpl;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.Objects;
@@ -117,6 +119,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter writer) {
         writer.println("**" + getClass().getSimpleName() + "**");
         synchronized (mLock) {
@@ -155,8 +158,6 @@
         if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
             return;
         }
-
-        IInstrumentCluster service;
         ContextOwner requester = new ContextOwner(uid, pid);
         ContextOwner newOwner = acquire ? requester : NO_OWNER;
         ClusterNavigationServiceCallback callback;
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index a36c898..4737b21 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -18,6 +18,8 @@
 import static android.car.cluster.renderer.InstrumentClusterRenderingService.EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER;
 import static android.car.settings.CarSettings.Global.DISABLE_INSTRUMENTATION_SERVICE;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.annotation.SystemApi;
 import android.app.ActivityOptions;
 import android.car.cluster.IInstrumentClusterManagerCallback;
@@ -52,6 +54,7 @@
 import com.android.car.R;
 import com.android.car.am.FixedActivityService;
 import com.android.car.cluster.ClusterNavigationService.ContextOwner;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -296,6 +299,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter writer) {
         writer.println("**" + getClass().getSimpleName() + "**");
         synchronized (mLock) {
diff --git a/service/src/com/android/car/evs/CarEvsService.java b/service/src/com/android/car/evs/CarEvsService.java
index 764fb08..b4235c9 100644
--- a/service/src/com/android/car/evs/CarEvsService.java
+++ b/service/src/com/android/car/evs/CarEvsService.java
@@ -23,6 +23,7 @@
 import static android.car.evs.CarEvsManager.SERVICE_STATE_INACTIVE;
 import static android.car.evs.CarEvsManager.SERVICE_STATE_REQUESTED;
 import static android.car.evs.CarEvsManager.SERVICE_STATE_UNAVAILABLE;
+import static android.car.evs.CarEvsManager.STREAM_EVENT_STREAM_STOPPED;
 
 import static com.android.car.CarLog.TAG_EVS;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -30,9 +31,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
-import android.car.hardware.CarPropertyValue;
-import android.car.hardware.property.CarPropertyEvent;
-import android.car.hardware.property.ICarPropertyEventListener;
 import android.car.evs.CarEvsBufferDescriptor;
 import android.car.evs.CarEvsManager;
 import android.car.evs.CarEvsManager.CarEvsError;
@@ -42,14 +40,19 @@
 import android.car.evs.CarEvsStatus;
 import android.car.evs.ICarEvsStatusListener;
 import android.car.evs.ICarEvsStreamCallback;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyEvent;
+import android.car.hardware.property.ICarPropertyEventListener;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.hardware.HardwareBuffer;
 import android.hardware.automotive.vehicle.V2_0.VehicleArea;
 import android.hardware.automotive.vehicle.V2_0.VehicleGear;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -151,11 +154,15 @@
         }
     }
 
+    private static final String COMMAND_TO_USE_DEFAULT_CAMERA = "default";
+
     private final Context mContext;
     private final EvsHalService mEvsHalService;
     private final CarPropertyService mPropertyService;
     private final Object mLock = new Object();
 
+    private final ComponentName mEvsCameraActivity;
+
     // This handler is to monitor the client sends a video stream request within a given time
     // after a state transition to the REQUESTED state.
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -424,8 +431,10 @@
                         // was transited to the ACTIVE state by a request that has the same priority
                         // with current request.
                         return ERROR_NONE;
+                    } else {
+                        // Stop stream on all lower priority clients.
+                        processStreamEvent(STREAM_EVENT_STREAM_STOPPED);
                     }
-
                     break;
 
                 default:
@@ -440,6 +449,20 @@
             mState = SERVICE_STATE_REQUESTED;
             mServiceType = service;
             mLastRequestPriority = priority;
+
+            if (mEvsCameraActivity != null) {
+                Intent evsIntent = new Intent(Intent.ACTION_MAIN)
+                        .setComponent(mEvsCameraActivity)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+                        .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+                        .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+                if (priority == REQUEST_PRIORITY_HIGH) {
+                    mSessionToken = new Binder();
+                    evsIntent.putExtra(CarEvsManager.EXTRA_SESSION_TOKEN, mSessionToken);
+                }
+                mContext.startActivity(evsIntent);
+            }
             return ERROR_NONE;
         }
 
@@ -463,7 +486,7 @@
 
                 case SERVICE_STATE_REQUESTED:
                     // CarEvsService is reserved for higher priority clients.
-                    if (!isSessionToken(token)) {
+                    if (priority == REQUEST_PRIORITY_HIGH && !isSessionToken(token)) {
                         // Declines a request with an expired token.
                         return ERROR_BUSY;
                     }
@@ -523,6 +546,12 @@
     // EVS_SERVICE_REQUEST.
     private boolean mUseGearSelection = true;
 
+    // When this is set, CarEvsService will attempt to open a camera device the user sets.
+    private boolean mUseCameraIdOverride = false;
+
+    // This is a device name to be used when mUseCameraIdOverride is true.
+    private String mCameraIdOverride;
+
     private void setSessionToken(IBinder token) {
         synchronized (mLock) {
             mSessionToken = token;
@@ -603,6 +632,14 @@
         mContext = context;
         mPropertyService = propertyService;
         mEvsHalService = halService;
+
+        String activityName = mContext.getResources().getString(R.string.config_evsCameraActivity);
+        if (!activityName.isEmpty()) {
+            mEvsCameraActivity = ComponentName.unflattenFromString(activityName);
+        } else {
+            mEvsCameraActivity = null;
+        }
+        if (DBG) Slog.d(TAG_EVS, "evsCameraActivity=" + mEvsCameraActivity);
     }
 
     /** Implements EvsHalService.EvsHalEventListener to monitor VHAL properties. */
@@ -960,6 +997,54 @@
         }
     }
 
+    /**
+     * Sets a camera device for the rearview.
+     *
+     * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
+     *
+     * @param id A string identifier of a target camera device.
+     * @return This method return a false if this runs in a release build; otherwise, this returns
+     *         true.
+     */
+    public boolean setRearviewCameraIdFromCommand(@NonNull String id) {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
+        Objects.requireNonNull(id);
+
+        if (!Build.IS_DEBUGGABLE) {
+            // This method is not allowed in the release build.
+            return false;
+        }
+
+        if (id.equalsIgnoreCase(COMMAND_TO_USE_DEFAULT_CAMERA)) {
+            mUseCameraIdOverride = false;
+            Slog.i(TAG_EVS, "CarEvsService is set to use the default device for the rearview.");
+        } else {
+            mCameraIdOverride = id;
+            mUseCameraIdOverride = true;
+            Slog.i(TAG_EVS, "CarEvsService is set to use " + id + " for the rearview.");
+        }
+
+        return true;
+    }
+
+    /**
+     * Gets an identifier of a current camera device for the rearview.
+     *
+     * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
+     * access.
+     *
+     * @return A string identifier of current rearview camera device.
+     */
+    @NonNull
+    public String getRearviewCameraIdFromCommand() {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
+        if (mUseCameraIdOverride) {
+            return mCameraIdOverride;
+        } else {
+            return mContext.getString(R.string.config_evsRearviewCameraId);
+        }
+    }
+
     /** Handles client disconnections; may request to stop a video stream. */
     private void handleClientDisconnected(ICarEvsStreamCallback callback) {
         // If the last stream client is disconnected before it stops a video stream, request to stop
@@ -995,8 +1080,14 @@
             return false;
         }
 
-        if (!nativeOpenCamera(mNativeEvsServiceObj,
-                mContext.getString(R.string.config_evsRearviewCameraId))) {
+        String cameraId;
+        if (mUseCameraIdOverride) {
+            cameraId = mCameraIdOverride;
+        } else {
+            cameraId = mContext.getString(R.string.config_evsRearviewCameraId);
+        }
+
+        if (!nativeOpenCamera(mNativeEvsServiceObj, cameraId)) {
             Slog.e(TAG_EVS, "Failed to open a target camera device");
             return false;
         }
diff --git a/service/src/com/android/car/hal/ClusterHalService.java b/service/src/com/android/car/hal/ClusterHalService.java
index 325b30d..75c06a9 100644
--- a/service/src/com/android/car/hal/ClusterHalService.java
+++ b/service/src/com/android/car/hal/ClusterHalService.java
@@ -22,6 +22,8 @@
 import static android.car.VehiclePropertyIds.CLUSTER_REQUEST_DISPLAY;
 import static android.car.VehiclePropertyIds.CLUSTER_SWITCH_UI;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.annotation.NonNull;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -31,6 +33,7 @@
 import android.os.SystemClock;
 import android.util.IntArray;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.utils.Slogf;
 
@@ -294,6 +297,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         writer.println("*Cluster HAL*");
         writer.println("mIsCoreSupported:" + isCoreSupported());
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
index bf0a7f2..f3dbc7c 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -16,6 +16,8 @@
 
 package com.android.car.hal;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import static java.lang.Integer.toHexString;
 
 import android.annotation.Nullable;
@@ -34,6 +36,7 @@
 
 import com.android.car.CarLog;
 import com.android.car.CarServiceUtils;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.internal.annotations.GuardedBy;
 
@@ -438,6 +441,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         writer.println("*Diagnostic HAL*");
     }
diff --git a/service/src/com/android/car/hal/EvsHalService.java b/service/src/com/android/car/hal/EvsHalService.java
index 011c6d8..dc1ccaf 100644
--- a/service/src/com/android/car/hal/EvsHalService.java
+++ b/service/src/com/android/car/hal/EvsHalService.java
@@ -16,22 +16,23 @@
 
 package com.android.car.hal;
 
-import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.EVS_SERVICE_REQUEST;
 import static android.car.evs.CarEvsManager.SERVICE_TYPE_REARVIEW;
 import static android.car.evs.CarEvsManager.SERVICE_TYPE_SURROUNDVIEW;
+import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.EVS_SERVICE_REQUEST;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
+import android.car.evs.CarEvsManager.CarEvsServiceType;
 import android.hardware.automotive.vehicle.V2_0.EvsServiceRequestIndex;
 import android.hardware.automotive.vehicle.V2_0.EvsServiceState;
 import android.hardware.automotive.vehicle.V2_0.EvsServiceType;
-
-import android.car.evs.CarEvsManager;
-import android.car.evs.CarEvsManager.CarEvsServiceType;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
@@ -189,6 +190,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         writer.println("*EVSHALSERVICE*");
         writer.printf("Use EVS_SERVICE_REQUEST: %b\n", isEvsServiceRequestSupported());
diff --git a/service/src/com/android/car/hal/InputHalService.java b/service/src/com/android/car/hal/InputHalService.java
index e3bed89..10fb466 100644
--- a/service/src/com/android/car/hal/InputHalService.java
+++ b/service/src/com/android/car/hal/InputHalService.java
@@ -24,6 +24,8 @@
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.HW_KEY_INPUT;
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.HW_ROTARY_INPUT;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.car.CarOccupantZoneManager;
 import android.car.input.CarInputManager;
 import android.car.input.CustomInputEvent;
@@ -39,6 +41,7 @@
 
 import com.android.car.CarLog;
 import com.android.car.CarServiceUtils;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.Slogf;
@@ -413,6 +416,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         writer.println("*Input HAL*");
         writer.println("mKeyInputSupported:" + mKeyInputSupported);
diff --git a/service/src/com/android/car/hal/PowerHalService.java b/service/src/com/android/car/hal/PowerHalService.java
index d95e766..a799f346 100644
--- a/service/src/com/android/car/hal/PowerHalService.java
+++ b/service/src/com/android/car/hal/PowerHalService.java
@@ -20,6 +20,8 @@
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AP_POWER_STATE_REQ;
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.DISPLAY_BRIGHTNESS;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.annotation.Nullable;
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateConfigFlag;
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReport;
@@ -33,6 +35,7 @@
 import android.util.Slog;
 
 import com.android.car.CarLog;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -467,6 +470,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         writer.println("*Power HAL*");
         writer.printf("isPowerStateSupported:%b, isDeepSleepAllowed:%b\n",
diff --git a/service/src/com/android/car/hal/PropertyHalService.java b/service/src/com/android/car/hal/PropertyHalService.java
index 446306b..a0399a3 100644
--- a/service/src/com/android/car/hal/PropertyHalService.java
+++ b/service/src/com/android/car/hal/PropertyHalService.java
@@ -19,6 +19,7 @@
 import static com.android.car.hal.CarPropertyUtils.toMixedCarPropertyValue;
 import static com.android.car.hal.CarPropertyUtils.toMixedVehiclePropValue;
 import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import static java.lang.Integer.toHexString;
 
@@ -41,6 +42,7 @@
 
 import com.android.car.CarLog;
 import com.android.car.CarServiceUtils;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
@@ -466,6 +468,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         writer.println(TAG);
         writer.println("  Properties available:");
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index 02f520b..0038099 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -21,6 +21,7 @@
 import static android.car.VehiclePropertyIds.SWITCH_USER;
 import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
@@ -63,6 +64,7 @@
 import com.android.car.CarLog;
 import com.android.car.CarServiceUtils;
 import com.android.car.CarStatsLog;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.internal.common.EventLogTags;
 import com.android.car.internal.common.UserHelperLite;
 import com.android.car.user.CarUserService;
@@ -1108,6 +1110,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         String indent = "  ";
         writer.printf("*User HAL*\n");
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 919fcf3..5de5a81 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -19,6 +19,7 @@
 import static com.android.car.CarServiceUtils.toByteArray;
 import static com.android.car.CarServiceUtils.toFloatArray;
 import static com.android.car.CarServiceUtils.toIntArray;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import static java.lang.Integer.toHexString;
 
@@ -48,6 +49,7 @@
 
 import com.android.car.CarLog;
 import com.android.car.CarServiceUtils;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -628,6 +630,7 @@
     /**
      * Dumps HAL service info using the print writer passed as parameter.
      */
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         writer.println("**dump HAL services**");
         for (HalServiceBase service: mAllServices) {
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index f6b50d4..de030bf 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -16,6 +16,7 @@
 package com.android.car.hal;
 
 import static com.android.car.CarServiceUtils.toByteArray;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.car.VehicleAreaType;
 import android.car.vms.VmsAssociatedLayer;
@@ -50,6 +51,7 @@
 import com.android.car.CarLocalServices;
 import com.android.car.CarLog;
 import com.android.car.CarServiceUtils;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.vms.VmsBrokerService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -218,6 +220,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(PrintWriter writer) {
         synchronized (mLock) {
             writer.println("*VMS HAL*");
@@ -238,6 +241,7 @@
      *
      * @param fd Dumpsys file descriptor to write client metrics to.
      */
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dumpMetrics(FileDescriptor fd) {
         if (mClientMetricsProperty == 0) {
             Slog.w(TAG, "Metrics collection is disabled");
diff --git a/service/src/com/android/car/power/CarPowerManagementService.java b/service/src/com/android/car/power/CarPowerManagementService.java
index b0c440b..b9d029b 100644
--- a/service/src/com/android/car/power/CarPowerManagementService.java
+++ b/service/src/com/android/car/power/CarPowerManagementService.java
@@ -1139,7 +1139,6 @@
 
     void notifySilentModeChange(boolean silent) {
         Slogf.i(TAG, "Silent mode is set to %b", silent);
-        mSilentModeHandler.updateKernelSilentMode(silent);
         if (silent) {
             applyPreemptivePowerPolicy(PolicyReader.POWER_POLICY_ID_NO_USER_INTERACTION);
         } else {
diff --git a/service/src/com/android/car/power/SilentModeHandler.java b/service/src/com/android/car/power/SilentModeHandler.java
index 11d6c8c..2631c01 100644
--- a/service/src/com/android/car/power/SilentModeHandler.java
+++ b/service/src/com/android/car/power/SilentModeHandler.java
@@ -246,6 +246,7 @@
                     newSilentMode = mSilentModeByHwState;
                 }
                 if (newSilentMode != oldSilentMode) {
+                    updateKernelSilentMode(newSilentMode);
                     mService.notifySilentModeChange(newSilentMode);
                 }
             }
diff --git a/service/src/com/android/car/telemetry/Channel.java b/service/src/com/android/car/telemetry/Channel.java
deleted file mode 100644
index 100e227..0000000
--- a/service/src/com/android/car/telemetry/Channel.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 com.android.car.telemetry;
-
-import android.os.Bundle;
-
-/**
- * Buffers data passing from publishers to subscribers. There is one channel per script receptor.
- *
- * TODO(b/187525360, b/187743369): make it a class or create an impl class for this.
- */
-public interface Channel {
-    /**
-     * Buffers the given data.
-     *
-     * TODO(b/189241508): Use ScriptExecutor supported data structure
-     */
-    void push(Bundle message);
-
-    /** Returns the publisher configuration for this channel. */
-    TelemetryProto.Publisher getPublisherConfig();
-
-    // TODO(b/187525360, b/187743369): Add other methods to check/get data from the channel.
-}
diff --git a/service/src/com/android/car/telemetry/databroker/DataBroker.java b/service/src/com/android/car/telemetry/databroker/DataBroker.java
index 4f96966..01535de 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBroker.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBroker.java
@@ -28,7 +28,7 @@
     /**
      * ScriptResultListener is the listener for script results.
      */
-    public interface ScriptResultListener {
+    interface ScriptResultListener {
         /**
          * Notifies listener of script result.
          *
@@ -50,13 +50,22 @@
 
     /**
      * Adds an active {@link com.android.car.telemetry.TelemetryProto.MetricsConfig} that is pending
-     * execution.
+     * execution. When updating the MetricsConfig to a newer version, the caller must call
+     * {@link #removeMetricsConfiguration(TelemetryProto.MetricsConfig)} first to clear the old
+     * MetricsConfig.
+     * TODO(b/191378559): Define behavior when metricsConfig contains invalid config
+     *
+     * @param metricsConfig to be added and queued for execution.
+     * @return true for success, false for failure.
      */
-    void addMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig);
+    boolean addMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig);
 
     /**
      * Removes a {@link com.android.car.telemetry.TelemetryProto.MetricsConfig} and all its
      * relevant subscriptions.
+     *
+     * @param metricsConfig to be removed from DataBroker.
+     * @return true for success, false for failure.
      */
-    void removeMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig);
+    boolean removeMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig);
 }
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
index ff12dc2..e76aa10 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
@@ -16,9 +16,17 @@
 
 package com.android.car.telemetry.databroker;
 
-import com.android.car.telemetry.TelemetryProto;
+import android.util.ArrayMap;
 
+import com.android.car.telemetry.TelemetryProto;
+import com.android.car.telemetry.TelemetryProto.MetricsConfig;
+import com.android.car.telemetry.publisher.LogcatPublisher;
+import com.android.car.telemetry.publisher.Publisher;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Implementation of the data path component of CarTelemetryService. Forwards the published data
@@ -28,6 +36,16 @@
 
     private final ScriptResultListener mScriptResultListener;
 
+    // Publisher is created per data source type. Publishers are kept alive once created. This map
+    // is used to check if a publisher already exists for a given type to prevent duplicate
+    // instantiation.
+    private final Map<TelemetryProto.Publisher.PublisherCase, Publisher> mPublisherMap =
+            new ArrayMap<>();
+
+    // Maps MetricsConfig's name to its subscriptions. This map is useful when removing a
+    // MetricsConfig.
+    private final Map<String, List<DataSubscriber>> mSubscriptionMap = new ArrayMap<>();
+
     public DataBrokerImpl(ScriptResultListener scriptResultListener) {
         mScriptResultListener = scriptResultListener;
     }
@@ -38,12 +56,70 @@
     }
 
     @Override
-    public void addMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig) {
-        // TODO(b/187743369): implement
+    public boolean addMetricsConfiguration(MetricsConfig metricsConfig) {
+        // if metricsConfig already exists, it should not be added again
+        if (mSubscriptionMap.containsKey(metricsConfig.getName())) {
+            return false;
+        }
+        // Create the subscribers for this metrics configuration
+        List<DataSubscriber> dataSubscribers = new ArrayList<>();
+        for (TelemetryProto.Subscriber subscriber : metricsConfig.getSubscribersList()) {
+            // protobuf publisher to a concrete Publisher
+            Publisher publisher = getOrCreatePublisherFromType(
+                    subscriber.getPublisher().getPublisherCase());
+
+            // create DataSubscriber from TelemetryProto.Subscriber
+            DataSubscriber dataSubscriber = new DataSubscriber(metricsConfig, subscriber);
+            dataSubscribers.add(dataSubscriber);
+
+            publisher.addSubscriber(dataSubscriber); // add subscriber to receive data
+        }
+        mSubscriptionMap.put(metricsConfig.getName(), dataSubscribers);
+        return true;
     }
 
     @Override
-    public void removeMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig) {
-        // TODO(b/187743369): implement
+    public boolean removeMetricsConfiguration(MetricsConfig metricsConfig) {
+        if (!mSubscriptionMap.containsKey(metricsConfig.getName())) {
+            return false;
+        }
+        // get the subscriptions associated with this MetricsConfig, remove it from the map
+        List<DataSubscriber> dataSubscribers = mSubscriptionMap.remove(metricsConfig.getName());
+        // for each subscriber, remove it from publishers
+        for (DataSubscriber subscriber : dataSubscribers) {
+            Publisher publisher = getOrCreatePublisherFromType(
+                    subscriber.getPublisherParam().getPublisherCase());
+            publisher.removeSubscriber(subscriber);
+            // TODO(b/187743369): remove related tasks from the queue
+        }
+        return true;
+    }
+
+    /**
+     * Gets and returns a {@link com.android.car.telemetry.publisher.Publisher} if it exists in
+     * the map, or creates one from the {@link com.android.car.telemetry.TelemetryProto.Publisher}'s
+     * type.
+     */
+    private Publisher getOrCreatePublisherFromType(
+            TelemetryProto.Publisher.PublisherCase type) {
+        Publisher publisher = mPublisherMap.get(type);
+        // check if publisher exists for this source
+        if (publisher != null) {
+            return publisher;
+        }
+        // TODO(b/187743369): use switch statement to create the correct publisher
+        publisher = new LogcatPublisher();
+        mPublisherMap.put(type, publisher);
+        return publisher;
+    }
+
+    @VisibleForTesting
+    Map<TelemetryProto.Publisher.PublisherCase, Publisher> getPublisherMap() {
+        return mPublisherMap;
+    }
+
+    @VisibleForTesting
+    Map<String, List<DataSubscriber>> getSubscriptionMap() {
+        return mSubscriptionMap;
     }
 }
diff --git a/service/src/com/android/car/telemetry/databroker/DataSubscriber.java b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
new file mode 100644
index 0000000..a32cc66
--- /dev/null
+++ b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.android.car.telemetry.databroker;
+
+import com.android.car.telemetry.TelemetryProto;
+
+/**
+ * Subscriber class that receive published data and schedules tasks for execution on the data.
+ */
+public class DataSubscriber {
+    private final TelemetryProto.Subscriber mSubscriber;
+
+    public DataSubscriber(TelemetryProto.MetricsConfig metricsConfig,
+            TelemetryProto.Subscriber subscriber) {
+        mSubscriber = subscriber;
+    }
+
+    /**
+     * Returns the publisher param {@link com.android.car.telemetry.TelemetryProto.Publisher} that
+     * contains the data source and the config.
+     */
+    public TelemetryProto.Publisher getPublisherParam() {
+        return mSubscriber.getPublisher();
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/LogcatPublisher.java b/service/src/com/android/car/telemetry/publisher/LogcatPublisher.java
index a8795c2..7bebe07 100644
--- a/service/src/com/android/car/telemetry/publisher/LogcatPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/LogcatPublisher.java
@@ -16,7 +16,7 @@
 
 package com.android.car.telemetry.publisher;
 
-import com.android.car.telemetry.Channel;
+import com.android.car.telemetry.databroker.DataSubscriber;
 
 /**
  * Publisher for Android logs (adb logcat).
@@ -24,13 +24,13 @@
  * TODO(b/187525360): Move the logic from LogcatReader here.
  */
 public class LogcatPublisher implements Publisher {
-    /** See {@link Publisher#addChannel()} for details. */
-    public void addChannel(Channel channel) {
+    @Override
+    public void addSubscriber(DataSubscriber dataSubscriber) {
         // TODO(b/187525360): implement
     }
 
-    /** See {@link Publisher#removeChannel()} for details. */
-    public void removeChannel(Channel channel) {
+    @Override
+    public void removeSubscriber(DataSubscriber dataSubscriber) {
         // TODO(b/187525360): implement
     }
 }
diff --git a/service/src/com/android/car/telemetry/publisher/Publisher.java b/service/src/com/android/car/telemetry/publisher/Publisher.java
index 5962711..d4d670e 100644
--- a/service/src/com/android/car/telemetry/publisher/Publisher.java
+++ b/service/src/com/android/car/telemetry/publisher/Publisher.java
@@ -16,30 +16,30 @@
 
 package com.android.car.telemetry.publisher;
 
-import com.android.car.telemetry.Channel;
+import com.android.car.telemetry.databroker.DataSubscriber;
 
 /**
- * Interface for publishers. It's implemented per data source, data is sent to {@link Channel}.
- * Publisher stops itself when there are no channels.
+ * Interface for publishers. It's implemented per data source, data is sent to
+ * {@link DataSubscriber}. Publisher stops itself when there are no subscribers.
  *
  * <p>Note that it doesn't map 1-1 to {@link TelemetryProto.Publisher} configuration. Single
  * publisher instance can send data as several {@link TelemetryProto.Publisher} to subscribers.
  */
 public interface Publisher {
+    /** Adds a subscriber that listens for data produced by this publisher. */
     /**
-     * Adds a channel to the publisher. Publisher will immediately start pushing
-     * data to the channel.
-     *
-     * @param channel a channel to publish data.
-     *
-     * @throws IllegalArgumentException if the channel was added before.
+     * Adds a subscriber that listens for data produced by this publisher.
+     * @param dataSubscriber a subscriber to receive data
+     * @throws IllegalArgumentException if the subscriber was added before.
      */
-    void addChannel(Channel channel);
+    void addSubscriber(DataSubscriber dataSubscriber);
 
+    /**  */
     /**
-     * Removes the channel from the publisher.
+     * Removes a subscriber from the publisher.
      *
-     * @throws IllegalArgumentException if the channel was not found.
+     * @param dataSubscriber to be removed
+     * @throws IllegalArgumentException if the subscriber was not found.
      */
-    void removeChannel(Channel channel);
+    void removeSubscriber(DataSubscriber dataSubscriber);
 }
diff --git a/service/src/com/android/car/user/CarUserNoticeService.java b/service/src/com/android/car/user/CarUserNoticeService.java
index a96478b..8148867 100644
--- a/service/src/com/android/car/user/CarUserNoticeService.java
+++ b/service/src/com/android/car/user/CarUserNoticeService.java
@@ -18,6 +18,8 @@
 
 import static android.car.hardware.power.CarPowerManager.CarPowerStateListener;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -53,6 +55,7 @@
 import com.android.car.CarLog;
 import com.android.car.CarServiceBase;
 import com.android.car.R;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -425,6 +428,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter writer) {
         synchronized (mLock) {
             if (mServiceIntent == null) {
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index de5739a..e61419b 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -22,6 +22,7 @@
 
 import static com.android.car.PermissionHelper.checkHasAtLeastOnePermissionGranted;
 import static com.android.car.PermissionHelper.checkHasDumpPermissionGranted;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -98,6 +99,7 @@
 import com.android.car.CarUxRestrictionsManagerService;
 import com.android.car.R;
 import com.android.car.hal.UserHalService;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.internal.ICarServiceHelper;
 import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
 import com.android.car.internal.common.EventLogTags;
@@ -351,6 +353,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(@NonNull IndentingPrintWriter writer) {
         checkHasDumpPermissionGranted("dump()");
 
diff --git a/service/src/com/android/car/user/InitialUserSetter.java b/service/src/com/android/car/user/InitialUserSetter.java
index 1d35f93..29406db 100644
--- a/service/src/com/android/car/user/InitialUserSetter.java
+++ b/service/src/com/android/car/user/InitialUserSetter.java
@@ -17,6 +17,8 @@
 
 import static android.car.userlib.UserHalHelper.userFlagsToString;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -39,6 +41,7 @@
 import android.util.TimingsTraceLog;
 
 import com.android.car.CarLog;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.internal.common.UserHelperLite;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
@@ -663,6 +666,7 @@
     /**
      * Dumps it state.
      */
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(@NonNull PrintWriter writer) {
         writer.println("InitialUserSetter");
         String indent = "  ";
diff --git a/tests/CarCtsFakeLauncher/src/com/android/car/fakelauncher/LauncherActivity.java b/tests/CarCtsFakeLauncher/src/com/android/car/fakelauncher/LauncherActivity.java
index 42d802a..0e73284 100644
--- a/tests/CarCtsFakeLauncher/src/com/android/car/fakelauncher/LauncherActivity.java
+++ b/tests/CarCtsFakeLauncher/src/com/android/car/fakelauncher/LauncherActivity.java
@@ -28,7 +28,7 @@
 /**
  * A placeholder launcher for CTS.
  *
- * Current CarLauncher invokes Maps Activity in its ActivityView and it creates unexpected events
+ * Current CarLauncher invokes Maps Activity in its TaskView and it creates unexpected events
  * for CTS and they make CTS fail.
  */
 public class LauncherActivity extends Activity {
diff --git a/tests/CarEvsCameraPreviewApp/AndroidManifest.xml b/tests/CarEvsCameraPreviewApp/AndroidManifest.xml
index bf54d88..6e4c254 100644
--- a/tests/CarEvsCameraPreviewApp/AndroidManifest.xml
+++ b/tests/CarEvsCameraPreviewApp/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <uses-permission android:name="android.car.permission.USE_CAR_EVS_CAMERA" />
     <uses-permission android:name="android.car.permission.MONITOR_CAR_EVS_STATUS" />
 
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+
     <application android:label="@string/app_name"
             android:icon="@drawable/rearview"
             android:hardwareAccelerated="true"
@@ -31,11 +33,10 @@
         <activity android:name=".CarEvsCameraPreviewActivity"
                 android:exported="true"
                 android:label="@string/app_name"
-                android:launchMode="singleTask"
                 android:resizeableActivity="false"
                 android:screenOrientation="landscape"
                 android:showForAllUsers="true"
-                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+                android:theme="@style/Theme.Transparent"
                 android:turnScreenOn="true">
         </activity>
 
diff --git a/tests/CarEvsCameraPreviewApp/res/layout/evs_preview_activity.xml b/tests/CarEvsCameraPreviewApp/res/layout/evs_preview_activity.xml
index 5745d8d..bfd138b 100644
--- a/tests/CarEvsCameraPreviewApp/res/layout/evs_preview_activity.xml
+++ b/tests/CarEvsCameraPreviewApp/res/layout/evs_preview_activity.xml
@@ -10,21 +10,18 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/evs_preview_container"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:background="@android:color/transparent"
+              android:orientation="vertical">
+    <Button
+        android:id="@+id/close_button"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="horizontal">
-      <TextureView
-        android:id="@+id/texture"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignParentTop="true" />
-      <SeekBar
-        android:id="@+id/saturationAdjustBar"
-        android:layout_width="600dp"
         android:layout_height="wrap_content"
-        android:layout_gravity="bottom|center_horizontal"
-        android:layout_marginBottom="100dp"
-        android:max="100"
-        android:progress="50"/>
-</FrameLayout>
+        android:text="@string/close_button_text"
+        android:textColor="@color/button_text"
+        android:background="@color/button_background"
+        android:textSize="@dimen/close_button_text_size"/>
+</LinearLayout>
diff --git a/tests/CarEvsCameraPreviewApp/res/values/colors.xml b/tests/CarEvsCameraPreviewApp/res/values/colors.xml
new file mode 100644
index 0000000..36632cb
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="button_text">@*android:color/car_body1_light</color>
+    <color name="button_background">@*android:color/car_card_dark</color>
+</resources>
diff --git a/tests/CarEvsCameraPreviewApp/res/values/dimens.xml b/tests/CarEvsCameraPreviewApp/res/values/dimens.xml
new file mode 100644
index 0000000..35bc2c4
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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.
+  -->
+<resources>
+    <!-- dimensions for evs camera preview in the system window -->
+    <dimen name="camera_preview_width">800dp</dimen>
+    <dimen name="camera_preview_height">600dp</dimen>
+
+    <dimen name="close_button_text_size">30sp</dimen>
+</resources>
\ No newline at end of file
diff --git a/tests/CarEvsCameraPreviewApp/res/values/strings.xml b/tests/CarEvsCameraPreviewApp/res/values/strings.xml
index c386e1d..4653f01 100644
--- a/tests/CarEvsCameraPreviewApp/res/values/strings.xml
+++ b/tests/CarEvsCameraPreviewApp/res/values/strings.xml
@@ -17,4 +17,5 @@
 
 <resources>
     <string name="app_name">EvsCameraPreview</string>
+    <string name="close_button_text">Close</string>
 </resources>
diff --git a/tests/CarEvsCameraPreviewApp/res/values/themes.xml b/tests/CarEvsCameraPreviewApp/res/values/themes.xml
new file mode 100644
index 0000000..14b98bb
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/res/values/themes.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <style name="Theme.Transparent" parent="android:Theme">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
index e34e4e8..bf6e950 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
@@ -25,12 +25,18 @@
 import android.car.evs.CarEvsBufferDescriptor;
 import android.car.evs.CarEvsManager;
 import android.content.Intent;
+import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Log;
 import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
 
 import java.util.ArrayList;
 import java.util.concurrent.ExecutorService;
@@ -48,7 +54,8 @@
     private final ExecutorService mCallbackExecutor = Executors.newFixedThreadPool(1);
 
     /** GL backed surface view to render the camera preview */
-    private CarEvsCameraGLSurfaceView mView;
+    private CarEvsCameraGLSurfaceView mEvsView;
+    private LinearLayout mPreviewContainer;
 
     /** Display manager to monitor the display's state */
     private DisplayManager mDisplayManager;
@@ -59,13 +66,12 @@
     /** Tells whether or not a video stream is running */
     private boolean mStreamRunning = false;
 
-    /** True if we need to start a video stream */
-    private boolean mActivityResumed = false;
-
     private Car mCar;
     private CarEvsManager mEvsManager;
 
-    private IBinder mSessiontoken;
+    private IBinder mSessionToken;
+
+    private boolean mUseSystemWindow;
 
     /** Callback to listen to EVS stream */
     private final CarEvsManager.CarEvsStreamCallback mStreamHandler =
@@ -75,6 +81,9 @@
         public void onStreamEvent(int event) {
             // This reference implementation only monitors a stream event without any action.
             Log.i(TAG, "Received: " + event);
+            if (event == CarEvsManager.STREAM_EVENT_STREAM_STOPPED) {
+                finish();
+            }
         }
 
         @Override
@@ -141,6 +150,8 @@
         Log.d(TAG, "onCreate");
         super.onCreate(savedInstanceState);
 
+        parseExtra(getIntent());
+
         setShowWhenLocked(true);
         mDisplayManager = getSystemService(DisplayManager.class);
         mDisplayManager.registerDisplayListener(mDisplayListener, null);
@@ -152,83 +163,97 @@
         Car.createCar(getApplicationContext(), /* handler = */ null,
                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener);
 
-        mView = new CarEvsCameraGLSurfaceView(getApplication(), this);
-        setContentView(mView);
+        mEvsView = new CarEvsCameraGLSurfaceView(getApplication(), this);
+        mPreviewContainer = (LinearLayout) LayoutInflater.from(this).inflate(
+                R.layout.evs_preview_activity, /* root= */ null);
+        LinearLayout.LayoutParams viewParam = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                1.0f
+        );
+        mEvsView.setLayoutParams(viewParam);
+        mPreviewContainer.addView(mEvsView, 0);
+        Button closeButton = mPreviewContainer.findViewById(R.id.close_button);
+        closeButton.setOnClickListener((v) -> finish());
 
-        setSessionToken(getIntent());
+        int width = WindowManager.LayoutParams.MATCH_PARENT;
+        int height = WindowManager.LayoutParams.MATCH_PARENT;
+        int x = 0;
+        int y = 0;
+        if (mUseSystemWindow) {
+            width = getResources().getDimensionPixelOffset(R.dimen.camera_preview_width);
+            height = getResources().getDimensionPixelOffset(R.dimen.camera_preview_height);
+            x = (getResources().getDisplayMetrics().widthPixels - width) / 2;
+            y = (getResources().getDisplayMetrics().heightPixels - height) / 2;
+        }
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                width, height, x, y,
+                2020 /* WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY */,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.TRANSLUCENT);
+        params.gravity = Gravity.LEFT | Gravity.TOP;
+        if (mUseSystemWindow) {
+            WindowManager wm = getSystemService(WindowManager.class);
+            wm.addView(mPreviewContainer, params);
+        } else {
+            setContentView(mPreviewContainer, params);
+        }
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
-        setSessionToken(intent);
+        parseExtra(intent);
     }
 
-    private void setSessionToken(Intent intent) {
+    private void parseExtra(Intent intent) {
         Bundle extras = intent.getExtras();
         if (extras == null) {
-            mSessiontoken = null;
+            mSessionToken = null;
             return;
         }
-        mSessiontoken = extras.getBinder(CarEvsManager.EXTRA_SESSION_TOKEN);
+        mSessionToken = extras.getBinder(CarEvsManager.EXTRA_SESSION_TOKEN);
+        mUseSystemWindow = mSessionToken != null;
     }
 
     @Override
     protected void onStart() {
         Log.d(TAG, "onStart");
         super.onStart();
+        handleVideoStreamLocked();
     }
 
-    @Override
-    protected void onResume() {
-        Log.d(TAG, "onResume");
-        super.onResume();
-
-        synchronized (mLock) {
-            mActivityResumed = true;
-            handleVideoStreamLocked();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        Log.d(TAG, "onPause");
-        super.onPause();
-
-        synchronized (mLock) {
-            mActivityResumed = false;
-            handleVideoStreamLocked();
-        }
-
-        synchronized (mBufferQueue) {
-            mBufferQueue.clear();
-        }
-    }
 
     @Override
     protected void onStop() {
         Log.d(TAG, "onStop");
         super.onStop();
-
-        // Request to stop current service and unregister a status listener
-        synchronized (mLock) {
-            if (mEvsManager != null) {
-                mEvsManager.stopActivity();
-                mEvsManager.clearStatusListener();
-            }
-        }
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
         Log.d(TAG, "onDestroy");
+        // Request to stop current service and unregister a status listener
+        synchronized (mBufferQueue) {
+            mBufferQueue.clear();
+        }
         synchronized (mLock) {
+            if (mEvsManager != null) {
+                mEvsManager.stopVideoStream();
+                mEvsManager.stopActivity();
+                mEvsManager.clearStatusListener();
+            }
             if (mCar != null) {
                 mCar.disconnect();
             }
         }
         mDisplayManager.unregisterDisplayListener(mDisplayListener);
+        if (mUseSystemWindow) {
+            WindowManager wm = getSystemService(WindowManager.class);
+            wm.removeView(mPreviewContainer);
+        }
     }
 
     private void handleVideoStreamLocked() {
@@ -237,13 +262,13 @@
             return;
         }
 
-        if (mActivityResumed && mDisplayState == Display.STATE_ON) {
+        if (mDisplayState == Display.STATE_ON) {
             // We show a camera preview only when the activity has been resumed and the display is
             // on.
             if (!mStreamRunning) {
                 Log.d(TAG, "Request to start a video stream");
                 mEvsManager.startVideoStream(CarEvsManager.SERVICE_TYPE_REARVIEW,
-                        mSessiontoken, mCallbackExecutor, mStreamHandler);
+                        mSessionToken, mCallbackExecutor, mStreamHandler);
                 mStreamRunning = true;
             }
 
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 098c7b2..af573f7 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -83,6 +83,8 @@
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+    <!-- Use for sensor access in Sensors fragment. -->
+    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS"/>
     <uses-permission android:name="android.permission.INJECT_EVENTS"/>
     <!-- use for CarServiceUnitTest and CarServiceTest -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
@@ -192,8 +194,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".activityview.ActivityViewTestFragment"/>
-
         <service android:name=".vendorservice.LogLifecycleService"
              android:exported="false"
              android:directBootAware="true">
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/activity_view_test_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/activity_view_test_fragment.xml
deleted file mode 100644
index 568bad5..0000000
--- a/tests/EmbeddedKitchenSinkApp/res/layout/activity_view_test_fragment.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
-
-    <LinearLayout android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:orientation="horizontal">
-        <Button android:id="@+id/av_launch_activity"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/av_start_activity"/>
-        <Button android:id="@+id/av_resize"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/av_resize"/>
-    </LinearLayout>
-    <RelativeLayout android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:padding="20dp">
-        <android.car.app.CarActivityView android:id="@+id/av_activityview"
-                      android:layout_width="match_parent"
-                      android:layout_height="match_parent"/>
-    </RelativeLayout>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/tests/NetworkPreferenceApp/res/values/configs.xml b/tests/NetworkPreferenceApp/res/values/configs.xml
index 21e55de..c2f30d5 100644
--- a/tests/NetworkPreferenceApp/res/values/configs.xml
+++ b/tests/NetworkPreferenceApp/res/values/configs.xml
@@ -40,10 +40,12 @@
     -->
     <string-array name="config_network_preference_oem_paid_apps" translatable="false">
         <!-- <item>full.package.name</item> -->
+        <item>com.android.car.settings</item>
         <item>com.android.vending</item>
         <item>com.google.android.apps.automotive.inputmethod</item>
         <item>com.google.android.apps.maps</item>
         <item>com.google.android.car.setupwizard</item>
+        <item>com.google.android.carassistant</item>
         <item>com.google.android.configupdater</item>
         <item>com.google.android.ext.services</item>
         <item>com.google.android.ext.shared</item>
diff --git a/tests/PowerTestService/src/main.cpp b/tests/PowerTestService/src/main.cpp
index 9a87408..5da764b 100644
--- a/tests/PowerTestService/src/main.cpp
+++ b/tests/PowerTestService/src/main.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "PowerTestService: "
+#define LOG_TAG "PowerTestService"
 
 #include <signal.h>
 #include <utils/Log.h>
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index 4685b62..7d34052 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -55,7 +55,7 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
-abstract class CarApiTestBase {
+public abstract class CarApiTestBase {
 
     private static final String TAG = CarApiTestBase.class.getSimpleName();
 
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
index eb331f5..0823c5f 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
@@ -189,12 +189,22 @@
 
     @NonNull
     protected UserInfo createUser() throws Exception {
-        return createUser("NonGuest", /* isGuest= */ false);
+        return createUser("NonGuest");
+    }
+
+    @NonNull
+    protected UserInfo createUser(String name) throws Exception {
+        return createUser(name, /* isGuest= */ false);
     }
 
     @NonNull
     protected UserInfo createGuest() throws Exception {
-        return createUser("Guest", /* isGuest= */ true);
+        return createGuest("Guest");
+    }
+
+    @NonNull
+    protected UserInfo createGuest(String name) throws Exception {
+        return createUser(name, /* isGuest= */ true);
     }
 
     @NonNull
@@ -323,6 +333,18 @@
         Log.v(TAG, "Set: " + SystemProperties.get(property));
     }
 
+
+    protected void assertUserInfo(UserInfo actualUser, UserInfo expectedUser) {
+        assertWithMessage("Wrong id for user %s", actualUser.toFullString())
+                .that(actualUser.id).isEqualTo(expectedUser.id);
+        assertWithMessage("Wrong name for user %s", actualUser.toFullString())
+                .that(actualUser.name).isEqualTo(expectedUser.name);
+        assertWithMessage("Wrong type for user %s", actualUser.toFullString())
+                .that(actualUser.userType).isEqualTo(expectedUser.userType);
+        assertWithMessage("Wrong flags for user %s", actualUser.toFullString())
+                .that(actualUser.flags).isEqualTo(expectedUser.flags);
+    }
+
     private static boolean isUserCreatedByTheseTests(UserInfo user) {
         return user.name != null && user.name.startsWith(NEW_USER_NAME_PREFIX);
     }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarSettingsTest.java b/tests/android_car_api_test/src/android/car/apitest/CarSettingsTest.java
new file mode 100644
index 0000000..a3070f0
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarSettingsTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.car.apitest;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
+import android.car.settings.CarSettings;
+import android.content.ContentResolver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+@SmallTest
+public final class CarSettingsTest extends CarApiTestBase {
+
+    private static final String TAG = CarSettingsTest.class.getSimpleName();
+    private static final String SCOPE_GLOBAL = "Global";
+    private static final String SCOPE_SECURE = "Secure";
+
+    private final HashMap<String, String> mSettings = new HashMap<>();
+    private final @UserIdInt int mUserId = UserHandle.USER_CURRENT;
+
+    private final ContentResolver mContentResolver;
+
+    public CarSettingsTest() throws Exception {
+        mContentResolver = getContext().getContentResolver();
+    }
+
+    @Test
+    public void testCarSettingsNames() throws Exception {
+        loadSettingNames();
+
+        boolean isAllSettingsReadable = checkAllSettingsReadable();
+
+        assertWithMessage("car settings readable").that(isAllSettingsReadable).isTrue();
+    }
+
+    private void loadSettingNames() {
+        mSettings.put(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, SCOPE_GLOBAL);
+        mSettings.put(CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, SCOPE_GLOBAL);
+        mSettings.put(CarSettings.Global.DISABLE_INSTRUMENTATION_SERVICE, SCOPE_GLOBAL);
+        mSettings.put(CarSettings.Global.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE, SCOPE_GLOBAL);
+        mSettings.put(CarSettings.Global.LAST_ACTIVE_USER_ID, SCOPE_GLOBAL);
+        mSettings.put(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID, SCOPE_GLOBAL);
+        mSettings.put(CarSettings.Secure.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL,
+                SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_AUDIO_PERSIST_VOLUME_GROUP_MUTE_STATES, SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS, SCOPE_SECURE);
+
+        mSettings.put(CarSettings.Secure.KEY_BLUETOOTH_A2DP_SINK_DEVICES, SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_BLUETOOTH_PAN_DEVICES, SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_BLUETOOTH_HFP_CLIENT_DEVICES, SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_BLUETOOTH_MAP_CLIENT_DEVICES, SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_BLUETOOTH_PBAP_CLIENT_DEVICES, SCOPE_SECURE);
+        mSettings.put(CarSettings.Secure.KEY_BLUETOOTH_PROFILES_INHIBITED, SCOPE_SECURE);
+    }
+
+    private boolean checkAllSettingsReadable() throws Exception {
+        Iterator<Map.Entry<String, String>> settingEntrys = mSettings.entrySet().iterator();
+        while (settingEntrys.hasNext()) {
+            Map.Entry<String, String> entry = settingEntrys.next();
+            String name = entry.getKey();
+            String scope = entry.getValue();
+            switch (scope) {
+                case SCOPE_GLOBAL:
+                    Settings.Global.getString(mContentResolver, name);
+                    break;
+                case SCOPE_SECURE:
+                    Settings.Secure.getStringForUser(mContentResolver, name, mUserId);
+                    break;
+                default:
+                    Log.e(TAG, "unsupported scope: " + scope);
+                    return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
index d66e3dd..62f2b0a 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
@@ -64,6 +64,34 @@
         }
     }
 
+    @Test
+    public void testCreateUser() throws Exception {
+        UserInfo newUser = createUser("DaNewUserInTheBlock");
+        assertWithMessage("(%s).isGuest()", newUser.toFullString()).that(newUser.isGuest())
+                .isFalse();
+
+        assertWithMessage("user(%s).name", newUser.toFullString()).that(newUser.name)
+                .contains("DaNewUserInTheBlock");
+
+        // Make sure the user exists
+        UserInfo loadedUser = getUser(newUser.id);
+        assertUserInfo(newUser, loadedUser);
+    }
+
+    @Test
+    public void testCreateGuest() throws Exception {
+        UserInfo newGuest = createGuest("DaNewGuestInTheBlock");
+        assertWithMessage("(%s).isGuest()", newGuest.toFullString()).that(newGuest.isGuest())
+                .isTrue();
+
+        assertWithMessage("guest(%s).name ", newGuest.toFullString()).that(newGuest.name)
+                .contains("DaNewGuestInTheBlock");
+
+        // Make sure the guest exists
+        UserInfo loadedGuest = getUser(newGuest.id);
+        assertUserInfo(newGuest, loadedGuest);
+    }
+
     /**
      * Tests resume behavior when current user is ephemeral guest, a new guest user should be
      * created and switched to.
diff --git a/tests/android_car_api_test/src/android/car/apitest/media/CarAudioManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/media/CarAudioManagerTest.java
new file mode 100644
index 0000000..9fb2781
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/media/CarAudioManagerTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.car.apitest.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.car.Car;
+import android.car.apitest.CarApiTestBase;
+import android.car.media.CarAudioManager;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
+import android.os.Process;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class CarAudioManagerTest extends CarApiTestBase {
+
+    private CarAudioManager mCarAudioManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+        assertThat(mCarAudioManager).isNotNull();
+    }
+
+    @Test
+    public void test_getAudioZoneIds() throws Exception {
+        assumeDynamicRoutingIsEnabled();
+
+        List<Integer> zoneIds = mCarAudioManager.getAudioZoneIds();
+        assertThat(zoneIds).isNotEmpty();
+        assertThat(zoneIds).contains(CarAudioManager.PRIMARY_AUDIO_ZONE);
+    }
+
+    @Test
+    public void test_isAudioFeatureEnabled() throws Exception {
+        // nothing to assert. Just call the API.
+        mCarAudioManager.isAudioFeatureEnabled(CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING);
+        mCarAudioManager.isAudioFeatureEnabled(CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING);
+    }
+
+    @Test
+    public void test_getVolumeGroupCount() throws Exception {
+        int primaryZoneCount = mCarAudioManager.getVolumeGroupCount();
+        assertThat(
+                mCarAudioManager.getVolumeGroupCount(CarAudioManager.PRIMARY_AUDIO_ZONE)).isEqualTo(
+                primaryZoneCount);
+    }
+
+    @Test
+    public void test_getGroupVolume() throws Exception {
+        int groudId = 0;
+        int volume = mCarAudioManager.getGroupVolume(groudId);
+        assertThat(mCarAudioManager.getGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
+                groudId)).isEqualTo(volume);
+        int maxVolume = mCarAudioManager.getGroupMaxVolume(groudId);
+        assertThat(mCarAudioManager.getGroupMaxVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
+                groudId)).isEqualTo(maxVolume);
+        int minVolume = mCarAudioManager.getGroupMinVolume(groudId);
+        assertThat(mCarAudioManager.getGroupMinVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
+                groudId)).isEqualTo(minVolume);
+        assertThat(volume).isAtLeast(minVolume);
+        assertThat(volume).isAtMost(maxVolume);
+    }
+
+    @Test
+    public void test_setGroupVolume() throws Exception {
+        int groudId = 0;
+        int volume = mCarAudioManager.getGroupVolume(groudId);
+        mCarAudioManager.setGroupVolume(groudId, volume, 0);
+        mCarAudioManager.setGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE, groudId, volume, 0);
+        assertThat(mCarAudioManager.getGroupVolume(groudId)).isEqualTo(volume);
+    }
+
+    @Test
+    public void test_getInputDevicesForZoneId() throws Exception {
+        assumeDynamicRoutingIsEnabled();
+
+        List<AudioDeviceInfo> info = mCarAudioManager.getInputDevicesForZoneId(
+                CarAudioManager.PRIMARY_AUDIO_ZONE);
+        assertThat(info).isNotEmpty();
+    }
+
+    @Test
+    public void test_getOutputDeviceForUsage() throws Exception {
+        assumeDynamicRoutingIsEnabled();
+
+        AudioDeviceInfo device = mCarAudioManager.getOutputDeviceForUsage(
+                CarAudioManager.PRIMARY_AUDIO_ZONE, AudioAttributes.USAGE_MEDIA);
+        assertThat(device).isNotNull();
+    }
+
+    @Test
+    public void test_getVolumeGroupIdForUsage() throws Exception {
+        int groupId = mCarAudioManager.getVolumeGroupIdForUsage(AudioAttributes.USAGE_MEDIA);
+        assertThat(mCarAudioManager.getVolumeGroupIdForUsage(CarAudioManager.PRIMARY_AUDIO_ZONE,
+                AudioAttributes.USAGE_MEDIA)).isEqualTo(groupId);
+        int primaryZoneCount = mCarAudioManager.getVolumeGroupCount();
+        assertThat(groupId).isLessThan(primaryZoneCount);
+    }
+
+    @Test
+    public void test_getZoneIdForUid() throws Exception {
+        assumeDynamicRoutingIsEnabled();
+
+        assertThat(mCarAudioManager.getZoneIdForUid(Process.myUid())).isEqualTo(
+                CarAudioManager.PRIMARY_AUDIO_ZONE);
+    }
+
+    @Test
+    public void test_isPlaybackOnVolumeGroupActive() throws Exception {
+        assumeDynamicRoutingIsEnabled();
+
+        // TODO(b/191660867): Better to change this to play something and asert true.
+        assertThat(
+                mCarAudioManager.isPlaybackOnVolumeGroupActive(CarAudioManager.PRIMARY_AUDIO_ZONE,
+                        0)).isFalse();
+    }
+
+    private void assumeDynamicRoutingIsEnabled() {
+        assumeTrue(mCarAudioManager.isAudioFeatureEnabled(
+                CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING));
+    }
+}
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 7133831..7bf749e 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -51,7 +51,6 @@
             </intent-filter>
         </service>
 
-        <activity android:name="com.android.car.CarUxRestrictionsManagerServiceTest$ActivityViewTestActivity"/>
         <activity android:name="com.android.car.pm.ActivityBlockingActivityTest$NonDoNoHistoryActivity"
              android:noHistory="true"/>
         <activity android:name="com.android.car.pm.ActivityBlockingActivityTest$NonDoActivity"/>
diff --git a/tests/carservice_test/src/com/android/car/CarEvsManagerTest.java b/tests/carservice_test/src/com/android/car/CarEvsManagerTest.java
index 8aeea95..cb15747 100644
--- a/tests/carservice_test/src/com/android/car/CarEvsManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarEvsManagerTest.java
@@ -96,7 +96,9 @@
 
     @After
     public void tearDown() throws Exception {
-        mEvsManager.stopVideoStream();
+        if (mEvsManager != null) {
+            mEvsManager.stopVideoStream();
+        }
     }
 
     @Test
diff --git a/tests/carservice_test/src/com/android/car/ICarImplTest.java b/tests/carservice_test/src/com/android/car/ICarImplTest.java
index 574b8ed..c0ba62a 100644
--- a/tests/carservice_test/src/com/android/car/ICarImplTest.java
+++ b/tests/carservice_test/src/com/android/car/ICarImplTest.java
@@ -165,7 +165,7 @@
         doThrow(new NullPointerException()).when(mContext).getDataDir();
 
         ICarImpl carImpl = new ICarImpl(mContext, mMockVehicle, mFakeSystemInterface,
-                /* errorNotifier= */ null, "MockedCar", /* carUserService= */ null,
+                "MockedCar", /* carUserService= */ null,
                 mCarWatchdogService, new MockedCarTestBase.FakeCarPowerPolicyDaemon());
         carImpl.init();
         Car mCar = new Car(mContext, carImpl, /* handler= */ null);
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index 9794b19..4119b8c 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -232,7 +232,7 @@
         // This should be done here as feature property is accessed inside the constructor.
         initMockedHal();
         mCarImpl = new ICarImpl(mMockedCarTestContext, mMockedVehicleHal, mFakeSystemInterface,
-                /* errorNotifier= */ null , "MockedCar", mCarUserService, mCarWatchdogService,
+                "MockedCar", mCarUserService, mCarWatchdogService,
                 mPowerPolicyDaemon);
 
         spyOnBeforeCarImplInit();
diff --git a/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
index c1b45d3..cd9adfb 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeServiceTest.java
@@ -60,6 +60,15 @@
     }
 
     @Test
+    public void testInitAndRelease() {
+        mService.init();
+        mService.release();
+
+        verify(mMockController).init();
+        verify(mMockController).release();
+    }
+
+    @Test
     public void testDump_shouldSucceed() {
         when(mMockController.isGarageModeActive()).thenReturn(true);
 
@@ -68,4 +77,33 @@
         List<String> strings = mCaptorString.getAllValues();
         assertThat(strings.get(0)).isEqualTo("GarageModeInProgress true");
     }
+
+    @Test
+    public void testIsGarageModeActive_true() {
+        when(mMockController.isGarageModeActive()).thenReturn(true);
+
+        assertThat(mService.isGarageModeActive()).isTrue();
+    }
+
+    @Test
+    public void testIsGarageModeActive_false() {
+        when(mMockController.isGarageModeActive()).thenReturn(false);
+
+        assertThat(mService.isGarageModeActive()).isFalse();
+    }
+
+    @Test
+    public void testForceStartGarageMode() {
+        mService.forceStartGarageMode();
+
+        verify(mMockController).initiateGarageMode(null);
+    }
+
+    @Test
+    public void testStopAndResetGarageMode() {
+        mService.stopAndResetGarageMode();
+
+        verify(mMockController).resetGarageMode();
+    }
+
 }
diff --git a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
index 499a95f..6133baf 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
@@ -98,10 +98,12 @@
         ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103));
         when(mCarUserService.startAllBackgroundUsersInGarageMode())
                 .thenReturn(userToStartInBackground);
-        mGarageMode.enterGarageMode(/* future= */ null);
+
         CountDownLatch latch = new CountDownLatch(3); // 3 for three users
         mockCarUserServiceStopUserCall(getEventListener(), latch);
 
+        mGarageMode.enterGarageMode(/* future= */ null);
+
         mGarageMode.cancel();
 
         waitForHandlerThreadToFinish(latch);
diff --git a/tests/carservice_test/src/com/android/car/input/CarInputManagerTest.java b/tests/carservice_test/src/com/android/car/input/CarInputManagerTest.java
index a9e678f..5cb55c3 100644
--- a/tests/carservice_test/src/com/android/car/input/CarInputManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/input/CarInputManagerTest.java
@@ -34,6 +34,7 @@
 import android.car.input.RotaryEvent;
 import android.hardware.automotive.vehicle.V2_0.VehicleDisplay;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.Pair;
 import android.view.KeyEvent;
@@ -50,6 +51,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -264,6 +266,50 @@
     }
 
     @Test
+    public void testInjectKeyEvent_mainDisplay() throws Exception {
+        int r = mCarInputManager.requestInputEventCapture(
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
+                new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
+                CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
+        assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
+
+        KeyEvent keyEvent = newKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
+
+        mCarInputManager.injectKeyEvent(keyEvent, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+
+        mCallback0.waitForKeyEvent();
+        assertThat(mCallback0.getkeyEvents()).containsExactly(
+                new Pair<>(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
+                        Collections.singletonList(keyEvent)));
+    }
+
+    @Test
+    public void testInjectKeyEvent_instrumentClusterDisplay() throws Exception {
+        int r = mCarInputManager.requestInputEventCapture(
+                CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
+                new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
+                CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
+        assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
+
+        KeyEvent keyEvent = newKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
+
+        mCarInputManager.injectKeyEvent(keyEvent,
+                CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
+
+        mCallback0.waitForKeyEvent();
+        assertThat(mCallback0.getkeyEvents()).containsExactly(
+                new Pair<>(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
+                        Collections.singletonList(keyEvent)));
+    }
+
+    private static KeyEvent newKeyEvent(int action, int code) {
+        long currentTime = SystemClock.uptimeMillis();
+        return new KeyEvent(/* downTime= */ currentTime,
+                /* eventTime= */ currentTime, action, code,
+                /* repeat= */ 0);
+    }
+
+    @Test
     public void testFailWithFullCaptureHigherPriority() {
         CarInputManager carInputManager0 = createAnotherCarInputManager();
         int r = carInputManager0.requestInputEventCapture(
diff --git a/tests/carservice_unit_test/src/android/car/util/concurrent/AndroidAsyncFutureTest.java b/tests/carservice_unit_test/src/android/car/util/concurrent/AndroidAsyncFutureTest.java
new file mode 100644
index 0000000..85b8d5e
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/util/concurrent/AndroidAsyncFutureTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.car.util.concurrent;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.internal.infra.AndroidFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+
+public final class AndroidAsyncFutureTest {
+
+    @Mock
+    private AndroidFuture<Integer> mFuture;
+
+    private MockitoSession mMockSession;
+
+    private AndroidAsyncFuture<Integer> mAndroidAsyncFuture;
+
+    @Before
+    public void setupMocks() {
+        mMockSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        mAndroidAsyncFuture = new AndroidAsyncFuture<Integer>(mFuture);
+    }
+
+    @After
+    public void tearDown() {
+        mMockSession.finishMocking();
+    }
+
+    @Test
+    public void testGet() throws Exception {
+        Integer input = 0;
+        when(mFuture.get()).thenReturn(input);
+
+        Integer result = mAndroidAsyncFuture.get();
+
+        assertThat(result).isEqualTo(input);
+        verify(mFuture).get();
+    }
+
+    @Test
+    public void testGetWithTimeout() throws Exception {
+        long timeout = 100;
+        TimeUnit timeUnit = TimeUnit.SECONDS;
+        Integer input = 0;
+        when(mFuture.get(timeout, timeUnit)).thenReturn(input);
+
+        Integer result = mAndroidAsyncFuture.get(timeout, timeUnit);
+
+        assertThat(result).isEqualTo(input);
+        verify(mFuture).get(timeout, timeUnit);
+    }
+
+    @Test
+    public void testWhenCompleteAsync() throws Exception {
+        Executor executor = (a) -> {};
+        BiConsumer<Integer, Throwable> biConsumer = (l, m) -> {};
+
+        AsyncFuture asyncFuture = mAndroidAsyncFuture.whenCompleteAsync(biConsumer, executor);
+
+        assertThat(asyncFuture).isEqualTo(mAndroidAsyncFuture);
+        verify(mFuture).whenCompleteAsync(biConsumer, executor);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarBugreportManagerServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarBugreportManagerServiceTest.java
new file mode 100644
index 0000000..7662bf6
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/CarBugreportManagerServiceTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.car.ICarBugreportCallback;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Unit tests for {@link CarBugreportManagerService}.
+ *
+ * <p>Run {@code atest CarServiceUnitTest:CarBugreportManagerServiceTest}.
+ */
+@SmallTest
+@RunWith(MockitoJUnitRunner.class)
+public class CarBugreportManagerServiceTest {
+    private static final boolean DUMPSTATE_DRY_RUN = true;
+
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private CarBugreportManagerService mService;
+
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
+    @Mock private PackageManager mMockPackageManager;
+    @Mock private ICarBugreportCallback mMockCallback;
+    @Mock private ParcelFileDescriptor mMockOutput;
+    @Mock private ParcelFileDescriptor mMockExtraOutput;
+
+    @Before
+    public void setUp() {
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+    }
+
+    @After
+    public void tearDown() {
+        if (mService != null) {
+            mService.release();
+        }
+    }
+
+    @Test
+    public void test_requestBugreport_failsIfNotDesignatedAppOnUserBuild() {
+        mService = new CarBugreportManagerService(mMockContext, /* isUserBuild= */ true);
+        mService.init();
+        when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+                .thenReturn(PackageManager.SIGNATURE_MATCH);
+        when(mMockPackageManager.getNameForUid(anyInt())).thenReturn("current_app_name");
+        when(mMockResources.getString(
+                R.string.config_car_bugreport_application)).thenReturn("random_app_name");
+
+        SecurityException expected =
+                expectThrows(SecurityException.class,
+                        () -> mService.requestBugreport(mMockOutput, mMockExtraOutput,
+                                mMockCallback, DUMPSTATE_DRY_RUN));
+
+        assertThat(expected).hasMessageThat().contains(
+                "Caller current_app_name is not a designated bugreport app");
+    }
+
+    @Test
+    public void test_requestBugreport_failsIfNotSignedWithPlatformKeys() {
+        mService = new CarBugreportManagerService(mMockContext);
+        mService.init();
+        when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+                .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+        when(mMockPackageManager.getNameForUid(anyInt())).thenReturn("current_app_name");
+
+        SecurityException expected =
+                expectThrows(SecurityException.class,
+                        () -> mService.requestBugreport(mMockOutput, mMockExtraOutput,
+                                mMockCallback, DUMPSTATE_DRY_RUN));
+
+        assertThat(expected).hasMessageThat().contains(
+                "Caller current_app_name does not have the right signature");
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
index 21da1d6..2e90523 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
@@ -17,6 +17,7 @@
 package com.android.car;
 
 import static android.car.CarOccupantZoneManager.DisplayTypeEnum;
+import static android.car.input.CustomInputEvent.INPUT_CODE_F1;
 
 import static com.android.compatibility.common.util.SystemUtil.eventually;
 
@@ -36,6 +37,7 @@
 import static org.mockito.Mockito.ignoreStubs;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -44,6 +46,10 @@
 import android.bluetooth.BluetoothProfile;
 import android.car.CarOccupantZoneManager;
 import android.car.CarProjectionManager;
+import android.car.input.CarInputManager;
+import android.car.input.CustomInputEvent;
+import android.car.input.ICarInputCallback;
+import android.car.input.RotaryEvent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -57,7 +63,6 @@
 import android.os.UserHandle;
 import android.service.voice.VoiceInteractionSession;
 import android.telecom.TelecomManager;
-import android.view.Display;
 import android.view.KeyEvent;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -68,6 +73,7 @@
 
 import com.google.common.collect.Range;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -120,6 +126,108 @@
         when(mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()).thenReturn(false);
     }
 
+    @After
+    public void tearDown() {
+        if (mCarInputService != null) {
+            mCarInputService.release();
+        }
+    }
+
+    @Test
+    public void testOnRotaryEvent_injectingRotaryNavigationEvent() {
+        RotaryEvent event = new RotaryEvent(
+                /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
+                /* clockwise= */ true,
+                /* uptimeMillisForClicks= */ new long[]{1, 1});
+        when(mCaptureController.onRotaryEvent(
+                same(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(event))).thenReturn(true);
+        when(mCaptureController.onKeyEvent(anyInt(), any(KeyEvent.class))).thenReturn(true);
+
+        mCarInputService.onRotaryEvent(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+
+        // Since mCaptureController processed RotaryEvent, then no KeyEvent was generated or
+        // processed
+        verify(mCarOccupantZoneService, never()).getDisplayIdForDriver(anyInt());
+        verify(mCaptureController, never()).onKeyEvent(anyInt(), any(KeyEvent.class));
+        verify(mDefaultMainListener, never()).onKeyEvent(any(KeyEvent.class));
+    }
+
+    @Test
+    public void testOnRotaryEvent_injectingRotaryVolumeEvent() {
+        RotaryEvent event = new RotaryEvent(
+                /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_VOLUME,
+                /* clockwise= */ true,
+                /* uptimeMillisForClicks= */ new long[]{1, 1});
+        when(mCaptureController.onRotaryEvent(
+                same(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(event))).thenReturn(false);
+        when(mCaptureController.onKeyEvent(anyInt(), any(KeyEvent.class))).thenReturn(true);
+
+        mCarInputService.onRotaryEvent(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+
+        // Since mCaptureController processed RotaryEvent, then KeyEvent was generated or
+        // processed
+        int numberOfGeneratedKeyEvents = 4;
+        verify(mCarOccupantZoneService, times(numberOfGeneratedKeyEvents)).getDisplayIdForDriver(
+                eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
+        verify(mCaptureController, times(numberOfGeneratedKeyEvents)).onKeyEvent(anyInt(),
+                any(KeyEvent.class));
+        verify(mDefaultMainListener, never()).onKeyEvent(any(KeyEvent.class));
+    }
+
+    @Test
+    public void testOnRotaryEvent_injectingRotaryNavigation_notConsumedByCaptureController() {
+        RotaryEvent event = new RotaryEvent(
+                /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
+                /* clockwise= */ true,
+                /* uptimeMillisForClicks= */ new long[]{1, 1});
+        when(mCaptureController.onRotaryEvent(
+                same(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(event))).thenReturn(false);
+
+        mCarInputService.onRotaryEvent(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+
+        // Since mCaptureController processed RotaryEvent, then KeyEvent was generated or
+        // processed
+        int numberOfGeneratedKeyEvents = 4;
+        verify(mCarOccupantZoneService, times(numberOfGeneratedKeyEvents)).getDisplayIdForDriver(
+                eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
+        verify(mCaptureController, times(numberOfGeneratedKeyEvents)).onKeyEvent(anyInt(),
+                any(KeyEvent.class));
+        verify(mDefaultMainListener, times(numberOfGeneratedKeyEvents)).onKeyEvent(
+                any(KeyEvent.class));
+    }
+
+    @Test
+    public void testRequestInputEventCapture_delegatesToCaptureController() {
+        ICarInputCallback callback = mock(ICarInputCallback.class);
+        int[] inputTypes = new int[]{CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT};
+        int requestFlags = CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT;
+        mCarInputService.requestInputEventCapture(callback,
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN, inputTypes, requestFlags);
+
+        verify(mCaptureController).requestInputEventCapture(same(callback),
+                eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(inputTypes), eq(requestFlags));
+    }
+
+    @Test
+    public void testOnCustomInputEvent_delegatesToCaptureController() {
+        CustomInputEvent event = new CustomInputEvent(INPUT_CODE_F1,
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN, /* repeatCounter= */ 1);
+
+        mCarInputService.onCustomInputEvent(event);
+
+        verify(mCaptureController).onCustomInputEvent(same(event));
+    }
+
+    @Test
+    public void testReleaseInputEventCapture_delegatesToCaptureController() {
+        ICarInputCallback callback = mock(ICarInputCallback.class);
+        mCarInputService.releaseInputEventCapture(callback,
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
+
+        verify(mCaptureController).releaseInputEventCapture(same(callback),
+                eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
+    }
+
     @Test
     public void ensureBluetoothAdapterWasInitialized() {
         eventually(() -> verify(mBluetoothAdapter).getProfileProxy(same(mContext),
@@ -570,7 +678,6 @@
         KeyEvent event = new KeyEvent(/* downTime= */ currentTime,
                 /* eventTime= */ currentTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
                 /* repeat= */ 0);
-
         event.setDisplayId(android.view.Display.INVALID_DISPLAY);
 
         injectKeyEventAndVerify(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
@@ -585,7 +692,7 @@
         KeyEvent event = new KeyEvent(/* downTime= */ currentTime,
                 /* eventTime= */ currentTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
                 /* repeat= */ 0);
-        event.setDisplayId(android.view.Display.DEFAULT_DISPLAY);
+        event.setDisplayId(android.view.Display.INVALID_DISPLAY);
 
         injectKeyEventAndVerify(event, CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
 
@@ -594,20 +701,17 @@
     }
 
     private void injectKeyEventAndVerify(KeyEvent event, @DisplayTypeEnum int displayType) {
-        // Arrange
         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
                 android.Manifest.permission.INJECT_EVENTS);
-
         int someDisplayId = Integer.MAX_VALUE;
         when(mCarOccupantZoneService.getDisplayIdForDriver(anyInt())).thenReturn(someDisplayId);
-
         assertThat(event.getDisplayId()).isNotEqualTo(someDisplayId);
 
-        // Act
         mCarInputService.injectKeyEvent(event, displayType);
 
-        // Assert display id was updated as expected
-        assertThat(event.getDisplayId()).isEqualTo(someDisplayId);
+        verify(mCarOccupantZoneService).getDisplayIdForDriver(displayType);
+        assertWithMessage("Event's display id not updated as expected").that(
+                event.getDisplayId()).isEqualTo(someDisplayId);
     }
 
     @Test
diff --git a/tests/carservice_unit_test/src/com/android/car/PermissionHelperTest.java b/tests/carservice_unit_test/src/com/android/car/PermissionHelperTest.java
new file mode 100644
index 0000000..d8ff974
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/PermissionHelperTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.android.car;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.testng.Assert.expectThrows;
+
+import android.app.ActivityManager;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.os.Binder;
+import android.util.Log;
+
+import org.junit.Test;
+
+public final class PermissionHelperTest extends AbstractExtendedMockitoTestCase {
+
+    private static final String TAG = PermissionHelperTest.class.getSimpleName();
+    private static final String MESSAGE = "D'OH!";
+
+    private static final int UID = Binder.getCallingUid();
+
+    private static final String PERMISSION1 = "LicenseToKill";
+    private static final String PERMISSION2 = "LicenseToLove";
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+        session.spyStatic(ActivityManager.class);
+    }
+
+    @Test
+    public void testHasAtLeastOnePermission_none() {
+        mockPermission(PERMISSION1, PERMISSION_DENIED);
+        mockPermission(PERMISSION2, PERMISSION_DENIED);
+
+        assertWithMessage("has at least %s", PERMISSION2).that(
+                PermissionHelper.hasAtLeastOnePermissionGranted(UID, PERMISSION1, PERMISSION2))
+                .isFalse();
+    }
+
+    @Test
+    public void testHasAtLeastOnePermission_one() {
+        mockPermission(PERMISSION1, PERMISSION_DENIED);
+        mockPermission(PERMISSION2, PERMISSION_GRANTED);
+
+        assertWithMessage("has at least %s", PERMISSION2).that(
+                PermissionHelper.hasAtLeastOnePermissionGranted(UID, PERMISSION1, PERMISSION2))
+                .isTrue();
+    }
+
+    @Test
+    public void testHasAtLeastOnePermission_both() {
+        mockPermission(PERMISSION1, PERMISSION_GRANTED);
+        mockPermission(PERMISSION2, PERMISSION_GRANTED);
+
+        assertWithMessage("has at least %s", PERMISSION2).that(
+                PermissionHelper.hasAtLeastOnePermissionGranted(UID, PERMISSION1, PERMISSION2))
+                .isTrue();
+    }
+
+    @Test
+    public void testCheckHasAtLeastOnePermission_none() {
+        mockPermission(PERMISSION1, PERMISSION_DENIED);
+        mockPermission(PERMISSION2, PERMISSION_DENIED);
+
+        SecurityException exception = expectThrows(SecurityException.class, () -> PermissionHelper
+                .checkHasAtLeastOnePermissionGranted(MESSAGE, PERMISSION1, PERMISSION2));
+
+        assertExceptionMessageContains(exception, MESSAGE);
+    }
+
+    @Test
+    public void testCheckHasAtLeastOnePermissionGranted_one() {
+        mockPermission(PERMISSION1, PERMISSION_DENIED);
+        mockPermission(PERMISSION2, PERMISSION_GRANTED);
+
+        PermissionHelper.checkHasAtLeastOnePermissionGranted(MESSAGE, PERMISSION1, PERMISSION2);
+    }
+
+    @Test
+    public void testCheckHasAtLeastOnePermissionGranted_both() {
+        mockPermission(PERMISSION1, PERMISSION_DENIED);
+        mockPermission(PERMISSION2, PERMISSION_GRANTED);
+
+        PermissionHelper.checkHasAtLeastOnePermissionGranted(MESSAGE, PERMISSION1, PERMISSION2);
+    }
+
+    @Test
+    public void testCheckHasDumpPermissionGranted_notGranted() {
+        mockPermission(android.Manifest.permission.DUMP, PERMISSION_DENIED);
+
+        SecurityException exception = expectThrows(SecurityException.class,
+                () -> PermissionHelper.checkHasDumpPermissionGranted(MESSAGE));
+
+        assertExceptionMessageContains(exception, MESSAGE);
+        assertExceptionMessageContains(exception, android.Manifest.permission.DUMP);
+    }
+
+    @Test
+    public void testCheckHasDumpPermissionGranted_granted() {
+        mockPermission(android.Manifest.permission.DUMP, PERMISSION_GRANTED);
+
+        PermissionHelper.checkHasDumpPermissionGranted(MESSAGE);
+    }
+
+    private void assertExceptionMessageContains(Exception exception, String subString) {
+        assertWithMessage("exception (%s) message", exception).that(exception.getMessage())
+                .contains(subString);
+    }
+
+    private void mockPermission(String permission, int value) {
+        Log.d(TAG, "mockHasPermissions(): uid=" + UID + ", permission=" + permission
+                + ", granted=" + (value == PERMISSION_GRANTED));
+
+        doReturn(value).when(() -> ActivityManager.checkComponentPermission(eq(permission),
+                eq(UID), anyInt(), anyBoolean()));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/SetMultimapTest.java b/tests/carservice_unit_test/src/com/android/car/SetMultimapTest.java
new file mode 100644
index 0000000..fa3a09c
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/SetMultimapTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+
+import org.junit.Test;
+
+public final class SetMultimapTest {
+
+    @Test
+    public void testGet_empty_returnsEmptySet() {
+        SetMultimap<Integer, String> map = new SetMultimap<>();
+
+        assertThat(map.get(1)).isEmpty();
+    }
+
+    @Test
+    public void testGet_keyDoesNotExist_returnsEmptySet() {
+        SetMultimap<Integer, String> map = createFromMultimap(ImmutableMultimap.of(1, "value1"));
+
+        assertThat(map.get(2)).isEmpty();
+    }
+
+    @Test
+    public void testGet() {
+        SetMultimap<Integer, String> map = new SetMultimap<>();
+
+        map.put(1, "value1");
+        assertThat(map.get(1)).containsExactly("value1");
+        map.put(1, "value2");
+        assertThat(map.get(1)).containsExactly("value1", "value2");
+        map.put(1, "value3");
+        assertThat(map.get(1)).containsExactly("value1", "value2", "value3");
+    }
+
+    @Test
+    public void testPut_duplicateValue() {
+        SetMultimap<Integer, String> map = new SetMultimap<>();
+        map.put(1, "value1");
+        map.put(1, "value1");
+
+        assertThat(map.get(1)).containsExactly("value1");
+    }
+
+    @Test
+    public void testContainsEntry() {
+        SetMultimap<Integer, String> map = createFromMultimap(ImmutableMultimap.of(1, "value1"));
+
+        assertThat(map.containsEntry(1, "value1")).isTrue();
+    }
+
+    @Test
+    public void testContainsEntry_keyDoesNotExist() {
+        SetMultimap<Integer, String> map = createFromMultimap(ImmutableMultimap.of(1, "value1"));
+
+        assertThat(map.containsEntry(2, "value1")).isFalse();
+    }
+
+    @Test
+    public void testContainsEntry_valueDoesNotExist() {
+        SetMultimap<Integer, String> map = createFromMultimap(ImmutableMultimap.of(1, "value1"));
+
+        assertThat(map.containsEntry(1, "value2")).isFalse();
+    }
+
+    @Test
+    public void testRemove_success() {
+        SetMultimap<Integer, String> map = createFromMultimap(
+                ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+
+        assertThat(map.remove(1, "value1")).isTrue();
+        assertContainsExactlyEntries(map, ImmutableMultimap.of(1, "value2", 2, "value3"));
+    }
+
+    @Test
+    public void testRemove_lastEntryOfKey() {
+        SetMultimap<Integer, String> map = createFromMultimap(
+                ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+
+        assertThat(map.remove(2, "value3")).isTrue();
+        assertContainsExactlyEntries(map, ImmutableMultimap.of(1, "value1", 1, "value2"));
+    }
+
+    @Test
+    public void testRemove_keyDoesNotExist() {
+        SetMultimap<Integer, String> map = createFromMultimap(
+                ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+
+        assertThat(map.remove(3, "value3")).isFalse();
+        assertContainsExactlyEntries(
+                map, ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+    }
+
+    @Test
+    public void testRemove_entryDoesNotExist() {
+        SetMultimap<Integer, String> map = createFromMultimap(
+                ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+
+        assertThat(map.remove(1, "value3")).isFalse();
+        assertContainsExactlyEntries(
+                map, ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+    }
+
+    @Test
+    public void testClear() {
+        SetMultimap<Integer, String> map = createFromMultimap(
+                ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+
+        map.clear();
+
+        assertContainsExactlyEntries(map, ImmutableMultimap.<Integer, String>of());
+    }
+
+    @Test
+    public void testKeySet() {
+        SetMultimap<Integer, String> map = createFromMultimap(
+                ImmutableMultimap.of(1, "value1", 1, "value2", 2, "value3"));
+
+        assertThat(map.keySet()).containsExactly(1, 2);
+    }
+
+    @Test
+    public void testKeySet_empty() {
+        SetMultimap<Integer, String> map = new SetMultimap<>();
+
+        assertThat(map.keySet()).isEmpty();
+    }
+
+    private static <K, V> SetMultimap<K, V> createFromMultimap(Multimap<K, V> multimap) {
+        SetMultimap<K, V> map = new SetMultimap<>();
+        multimap.entries().forEach(entry -> map.put(entry.getKey(), entry.getValue()));
+
+        return map;
+    }
+
+    private static <K, V> void assertContainsExactlyEntries(
+            SetMultimap<K, V> actual, Multimap<K, V> expected) {
+        ImmutableMultimap.Builder<K, V> multimapBuilder = ImmutableMultimap.builder();
+        actual.keySet().forEach(key -> multimapBuilder.putAll(key, actual.get(key)));
+
+        assertThat(multimapBuilder.build()).containsExactlyEntriesIn(expected);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/SparseArrayStreamTest.java b/tests/carservice_unit_test/src/com/android/car/SparseArrayStreamTest.java
new file mode 100644
index 0000000..3016027
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/SparseArrayStreamTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.stream.Collectors.toList;
+
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+public final class SparseArrayStreamTest {
+
+    @Test
+    public void testKeyStream() {
+        SparseArray<String> array = createSparseStringArray();
+        ImmutableList<Integer> expected = ImmutableList.of(5, 20, 1000);
+
+        IntStream actual = SparseArrayStream.keyStream(array);
+
+        assertContainsExactlyInOrder(actual.boxed(), expected);
+    }
+
+    @Test
+    public void testKeyStream_empty() {
+        assertContainsExactlyInOrder(
+                SparseArrayStream.keyStream(new SparseArray<String>()).boxed(), ImmutableList.of());
+    }
+
+    @Test
+    public void testValueStream() {
+        SparseArray<String> array = createSparseStringArray();
+        ImmutableList<String> expected = ImmutableList.of("five", "twenty", "thousand");
+
+        Stream<String> actual = SparseArrayStream.valueStream(array);
+
+        assertContainsExactlyInOrder(actual, expected);
+    }
+
+    @Test
+    public void testValueStream_empty() {
+        assertContainsExactlyInOrder(
+                SparseArrayStream.valueStream(new SparseArray<String>()), ImmutableList.of());
+    }
+
+    @Test
+    public void testPairStream() {
+        SparseArray<String> array = createSparseStringArray();
+        ImmutableList<Pair<Integer, String>> expected = ImmutableList.of(
+                new Pair<>(5, "five"), new Pair<>(20, "twenty"), new Pair<>(1000, "thousand"));
+
+        Stream<Pair<Integer, String>> actual = SparseArrayStream.pairStream(array);
+
+        assertContainsExactlyInOrder(actual, expected);
+    }
+
+    @Test
+    public void testPairStream_empty() {
+        assertContainsExactlyInOrder(
+                SparseArrayStream.pairStream(new SparseArray<String>()), ImmutableList.of());
+    }
+
+    private static SparseArray<String> createSparseStringArray() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(5, "five");
+        array.put(20, "twenty");
+        array.put(1000, "thousand");
+
+        return array;
+    }
+
+    private static <T> void assertContainsExactlyInOrder(Stream<T> actual, List<T> expected) {
+        // TODO: Use Truth8 StreamSubject when it becomes available.
+        assertThat(actual.collect(toList())).containsExactlyElementsIn(expected).inOrder();
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/UtilsGetAdapterStateNameTest.java b/tests/carservice_unit_test/src/com/android/car/UtilsGetAdapterStateNameTest.java
new file mode 100644
index 0000000..c536e9a
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/UtilsGetAdapterStateNameTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+
+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 UtilsGetAdapterStateNameTest {
+
+    private final int mState;
+    private final String mName;
+
+    public UtilsGetAdapterStateNameTest(int state, String name) {
+        mState = state;
+        mName = name;
+    }
+
+    @Test
+    public void testGetAdapterStateName() {
+        String result = Utils.getAdapterStateName(mState);
+
+        assertThat(result).contains(String.valueOf(mState));
+        assertThat(result).ignoringCase().contains(mName);
+    }
+
+    @Parameterized.Parameters
+    public static Collection provideParams() {
+        return Arrays.asList(
+            new Object[][] {
+                {BluetoothAdapter.STATE_ON, "on"},
+                {BluetoothAdapter.STATE_OFF, "off"},
+                {BluetoothAdapter.STATE_TURNING_ON, "turning on"},
+                {BluetoothAdapter.STATE_TURNING_OFF, "turning off"},
+                {9, "unknown"},
+                {14, "unknown"}
+            });
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/UtilsGetBondStateNameTest.java b/tests/carservice_unit_test/src/com/android/car/UtilsGetBondStateNameTest.java
new file mode 100644
index 0000000..9a89e59
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/UtilsGetBondStateNameTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothDevice;
+
+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 UtilsGetBondStateNameTest {
+
+    private final int mState;
+    private final String mName;
+
+    public UtilsGetBondStateNameTest(int state, String name) {
+        mState = state;
+        mName = name;
+    }
+
+    @Test
+    public void testGetBondStateName() {
+        String result = Utils.getBondStateName(mState);
+
+        assertThat(result).contains(String.valueOf(mState));
+        assertThat(result).ignoringCase().contains(mName);
+    }
+
+    @Parameterized.Parameters
+    public static Collection provideParams() {
+        return Arrays.asList(
+            new Object[][] {
+                {BluetoothDevice.BOND_BONDED, "bonded"},
+                {BluetoothDevice.BOND_BONDING, "bonding"},
+                {BluetoothDevice.BOND_NONE, "unbonded"},
+                {9, "unknown"},
+                {13, "unknown"}
+            });
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/UtilsGetConnectionStateNameTest.java b/tests/carservice_unit_test/src/com/android/car/UtilsGetConnectionStateNameTest.java
new file mode 100644
index 0000000..74bfffb
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/UtilsGetConnectionStateNameTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+
+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 UtilsGetConnectionStateNameTest {
+
+    private final int mState;
+    private final String mName;
+
+    public UtilsGetConnectionStateNameTest(int state, String name) {
+        mState = state;
+        mName = name;
+    }
+
+    @Test
+    public void testGetConnectionStateName() {
+        String result = Utils.getConnectionStateName(mState);
+
+        assertThat(result).contains(String.valueOf(mState));
+        assertThat(result).ignoringCase().contains(mName);
+    }
+
+    @Parameterized.Parameters
+    public static Collection provideParams() {
+        return Arrays.asList(
+            new Object[][] {
+                {BluetoothAdapter.STATE_CONNECTED, "connected"},
+                {BluetoothAdapter.STATE_DISCONNECTED, "disconnected"},
+                {BluetoothAdapter.STATE_CONNECTING, "connecting"},
+                {BluetoothAdapter.STATE_DISCONNECTING, "disconnecting"},
+                {-1, "unknown"},
+                {4, "unknown"}
+            });
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/UtilsGetProfileNameTest.java b/tests/carservice_unit_test/src/com/android/car/UtilsGetProfileNameTest.java
new file mode 100644
index 0000000..54220b3
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/UtilsGetProfileNameTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothProfile;
+
+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 UtilsGetProfileNameTest {
+
+    private final int mProfile;
+    private final String mName;
+
+    public UtilsGetProfileNameTest(int profile, String name) {
+        mProfile = profile;
+        mName = name;
+    }
+
+    @Test
+    public void testGetProfileName() {
+        String result = Utils.getProfileName(mProfile);
+
+        assertThat(result).contains(String.valueOf(mProfile));
+        assertThat(result).ignoringCase().contains(mName);
+    }
+
+    @Parameterized.Parameters
+    public static Collection provideParams() {
+        return Arrays.asList(
+            new Object[][] {
+                {BluetoothProfile.HEADSET, "HFP Server"},
+                {BluetoothProfile.A2DP, "A2DP Source"},
+                {BluetoothProfile.HEALTH, "HDP"},
+                {BluetoothProfile.HID_HOST, "HID Host"},
+                {BluetoothProfile.PAN, "PAN"},
+                {BluetoothProfile.PBAP, "PBAP Server"},
+                {BluetoothProfile.GATT, "GATT Client"},
+                {BluetoothProfile.GATT_SERVER, "GATT Server"},
+                {BluetoothProfile.MAP, "MAP Server"},
+                {BluetoothProfile.SAP, "SAP"},
+                {BluetoothProfile.A2DP_SINK, "A2DP Sink"},
+                {BluetoothProfile.AVRCP_CONTROLLER, "AVRCP Controller"},
+                {BluetoothProfile.AVRCP, "AVRCP Target"},
+                {BluetoothProfile.HEADSET_CLIENT, "HFP Client"},
+                {BluetoothProfile.PBAP_CLIENT, "PBAP Client"},
+                {BluetoothProfile.MAP_CLIENT, "MAP Client"},
+                {BluetoothProfile.HID_DEVICE, "HID Device"},
+                {BluetoothProfile.OPP, "OPP"},
+                {BluetoothProfile.HEARING_AID, "Hearing Aid"},
+                {0, "unknown"},
+                {22, "unknown"}
+            });
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/UtilsGetProfilePriorityNameTest.java b/tests/carservice_unit_test/src/com/android/car/UtilsGetProfilePriorityNameTest.java
new file mode 100644
index 0000000..4ed8175
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/UtilsGetProfilePriorityNameTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothProfile;
+
+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 UtilsGetProfilePriorityNameTest {
+
+    private final int mPriority;
+    private final String mName;
+
+    public UtilsGetProfilePriorityNameTest(int priority, String name) {
+        mPriority = priority;
+        mName = name;
+    }
+
+    @Test
+    public void testGetProfilePriorityName() {
+        String result = Utils.getProfilePriorityName(mPriority);
+
+        assertThat(result).contains(String.valueOf(mPriority));
+        assertThat(result).ignoringCase().contains(mName);
+    }
+
+    @Parameterized.Parameters
+    public static Collection provideParams() {
+        return Arrays.asList(
+            new Object[][] {
+                // Any value >= PRIORITY_AUTO_CONNECT maps to "PRIORITY_AUTO_CONNECT".
+                {1001, "PRIORITY_AUTO_CONNECT"},
+                {BluetoothProfile.PRIORITY_AUTO_CONNECT, "PRIORITY_AUTO_CONNECT"},
+                // PRIORITY_AUTO_CONNECT > value >= PRIORITY_ON maps to "PRIORITY_ON".
+                {999, "PRIORITY_ON"},
+                {BluetoothProfile.PRIORITY_ON, "PRIORITY_ON"},
+                // PRIORITY_ON > value >= PRIORITY_OFF mpas to "PRIORITY_OFF".
+                {99, "PRIORITY_OFF"},
+                {BluetoothProfile.PRIORITY_OFF, "PRIORITY_OFF"},
+                // value < PRIORITY_OFF maps to "PRIORITY_UNDEFINED".
+                {BluetoothProfile.PRIORITY_UNDEFINED, "PRIORITY_UNDEFINED"},
+                {-2, "PRIORITY_UNDEFINED"}
+            });
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/UtilsTest.java b/tests/carservice_unit_test/src/com/android/car/UtilsTest.java
new file mode 100644
index 0000000..16285b4
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/UtilsTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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 com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.text.TextUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.UUID;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class UtilsTest {
+
+    @Mock
+    private BluetoothDevice mMockBluetoothDevice;
+
+    @Test
+    public void testGetDeviceDebugInfo() {
+        when(mMockBluetoothDevice.getName()).thenReturn("deviceName");
+        when(mMockBluetoothDevice.getAddress()).thenReturn("deviceAddress");
+
+        assertThat(Utils.getDeviceDebugInfo(mMockBluetoothDevice))
+            .isEqualTo("(name = deviceName, addr = deviceAddress)");
+    }
+
+    @Test
+    public void testGetDeviceDebugInfo_nullDevice() {
+        assertThat(Utils.getDeviceDebugInfo(null)).isEqualTo("(null)");
+    }
+
+    @Test
+    public void testTransitionLogToString() {
+        Utils.TransitionLog transitionLog =
+                new Utils.TransitionLog("serviceName", "state1", "state2", 1623777864000L);
+        String result = transitionLog.toString();
+
+        assertThat(result).startsWith("06-15 17:24:24");
+        assertThat(result).contains("serviceName:");
+        assertThat(result).contains("from state1 to state2");
+    }
+
+    @Test
+    public void testTransitionLogToString_withExtra() {
+        Utils.TransitionLog transitionLog =
+                new Utils.TransitionLog("serviceName", "state1", "state2", 1623777864000L, "extra");
+        String result = transitionLog.toString();
+
+        assertThat(result).startsWith("06-15 17:24:24");
+        assertThat(result).contains("serviceName:");
+        assertThat(result).contains("extra");
+        assertThat(result).contains("from state1 to state2");
+    }
+
+    @Test
+    public void testLongToBytes() {
+        long longValue = 1234567890L;
+        byte[] expected = new byte[] {0, 0, 0, 0, 73, -106, 2, -46};
+
+        assertThat(Utils.longToBytes(longValue)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testBytesToLong() {
+        byte[] bytes = new byte[] {0, 0, 0, 0, 73, -106, 2, -46};
+        long expected = 1234567890L;
+
+        assertThat(Utils.bytesToLong(bytes)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testByteArrayToHexString() {
+        assertThat(Utils.byteArrayToHexString(new byte[] {0, 1, 2, -3})).isEqualTo("000102fd");
+    }
+
+    @Test
+    public void testUuidToBytes() {
+        UUID uuid = new UUID(123456789L, 987654321L);
+        byte[] expected = new byte[] {0, 0, 0, 0, 7, 91, -51, 21, 0, 0, 0, 0, 58, -34, 104, -79};
+
+        assertThat(Utils.uuidToBytes(uuid)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testBytesToUUID() {
+        byte[] bytes = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, -9, -8, -7, -6, -5, -4, -3};
+        UUID expected = new UUID(72623859790382856L, 718316418130246909L);
+
+        assertThat(Utils.bytesToUUID(bytes).getLeastSignificantBits())
+                .isEqualTo(718316418130246909L);
+        assertThat(Utils.bytesToUUID(bytes).getMostSignificantBits()).isEqualTo(72623859790382856L);
+        assertThat(Utils.bytesToUUID(bytes)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testBytesToUUID_invalidLength() {
+        byte[] bytes = new byte[] {0};
+
+        assertThat(Utils.bytesToUUID(bytes)).isNull();
+    }
+
+    @Test
+    public void testGenerateRandomNumberString() {
+        String result = Utils.generateRandomNumberString(25);
+
+        assertThat(result).hasLength(25);
+        assertThat(TextUtils.isDigitsOnly(result)).isTrue();
+    }
+
+    @Test
+    public void testConcatByteArrays() {
+        byte[] bytes1 = new byte[] {1, 2, 3};
+        byte[] bytes2 = new byte[] {4, 5, 6};
+        Byte[] expected = new Byte[] {1, 2, 3, 4, 5, 6};
+
+        assertThat(Utils.concatByteArrays(bytes1, bytes2)).asList()
+                .containsExactlyElementsIn(expected).inOrder();
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java
new file mode 100644
index 0000000..b1e8b72
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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 com.android.car.admin;
+
+import static android.app.Notification.EXTRA_TEXT;
+import static android.app.Notification.EXTRA_TITLE;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.android.car.admin.NotificationHelper.CHANNEL_ID_DEFAULT;
+import static com.android.car.admin.NotificationHelper.NEW_USER_DISCLAIMER_NOTIFICATION_ID;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.UiAutomation;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.mocks.JavaMockitoHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.R;
+import com.android.car.admin.ui.ManagedDeviceTextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+public final class NewUserDisclaimerActivityTest extends AbstractExtendedMockitoTestCase {
+
+    private static final String TAG = NewUserDisclaimerActivityTest.class.getSimpleName();
+
+    private static final long TIMEOUT_MS = 1_000;
+
+    private final Context mRealContext = InstrumentationRegistry.getInstrumentation()
+            .getContext();
+
+    private final UiAutomation mUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+    // NOTE: Cannot launch activity automatically as we need to mock
+    // PerUserCarDevicePolicyService.getInstance() first
+    @Rule
+    public ActivityTestRule<NewUserDisclaimerActivity> mActivityRule = new ActivityTestRule(
+            NewUserDisclaimerActivity.class,  /* initialTouchMode= */ false,
+            /* launchActivity= */ false);
+
+    private NewUserDisclaimerActivity mActivity;
+
+    private Context mSpiedContext;
+
+    @Mock
+    private PerUserCarDevicePolicyService mService;
+
+    @Mock
+    private NotificationManager mNotificationManager;
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+        session.spyStatic(PerUserCarDevicePolicyService.class);
+    }
+
+    @Before
+    public void setFixtures() {
+        Log.v(TAG, "setFixtures(): mocking PerUserCarDevicePolicyService.getInstance()");
+        doReturn(mService).when(() -> PerUserCarDevicePolicyService.getInstance(any()));
+        mSpiedContext = spy(mRealContext);
+
+        when(mSpiedContext.getSystemService(NotificationManager.class))
+                .thenReturn(mNotificationManager);
+
+        Log.v(TAG, "setFixtures(): launching activitiy");
+        mActivity = mActivityRule.launchActivity(/* intent= */ null);
+
+        // It's called onResume()
+        verify(mService).setShown();
+    }
+
+    @Test
+    public void testAccept() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        mActivity.runOnUiThread(() -> {
+            mActivity.onCreate(/* savedInstanceState= */ null);
+            Button button = mActivity.getAcceptButton();
+            Log.d(TAG, "Clicking accept button: " + button);
+            button.performClick();
+            latch.countDown();
+        });
+        JavaMockitoHelper.await(latch, TIMEOUT_MS);
+
+        verify(mService).setAcknowledged();
+        assertWithMessage("activity is finishing").that(mActivity.isFinishing()).isTrue();
+    }
+
+    @Test
+    public void testShowNotification() {
+        NewUserDisclaimerActivity.showNotification(mSpiedContext);
+
+        ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager).notify(eq(NEW_USER_DISCLAIMER_NOTIFICATION_ID),
+                captor.capture());
+
+        Notification notification = captor.getValue();
+        assertWithMessage("notification").that(notification).isNotNull();
+        assertNotificationContents(notification);
+    }
+
+    @Test
+    public void testCancelNotification() throws Exception {
+        PendingIntent pendingIntent = NewUserDisclaimerActivity.getPendingIntent(mSpiedContext,
+                /* extraFlags = */ 0);
+        CountDownLatch cancelLatch = new CountDownLatch(1);
+        pendingIntent.registerCancelListener(pi -> cancelLatch.countDown());
+
+        NewUserDisclaimerActivity.cancelNotification(mSpiedContext);
+
+        verify(mNotificationManager).cancel(NEW_USER_DISCLAIMER_NOTIFICATION_ID);
+
+        // Assert pending intent was canceled (latch is counted down by the CancelListener)
+        JavaMockitoHelper.await(cancelLatch, TIMEOUT_MS);
+    }
+
+    private void assertNotificationContents(Notification notification) {
+        assertWithMessage("notification icon").that(notification.getSmallIcon()).isNotNull();
+        assertWithMessage("notification channel").that(notification.getChannelId())
+                .isEqualTo(CHANNEL_ID_DEFAULT);
+        assertWithMessage("notification flags has FLAG_ONGOING_EVENT")
+                .that(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(FLAG_ONGOING_EVENT);
+
+        assertWithMessage("notification content pending intent")
+                .that(notification.contentIntent)
+                .isNotNull();
+        assertWithMessage("notification content pending intent is immutable")
+                .that(notification.contentIntent.isImmutable()).isTrue();
+        // Need android.permission.GET_INTENT_SENDER_INTENT to get the Intent
+        Intent intent;
+        mUiAutomation.adoptShellPermissionIdentity();
+        try {
+            intent = notification.contentIntent.getIntent();
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+        assertWithMessage("content intent").that(intent).isNotNull();
+        assertWithMessage("content intent component").that(intent.getComponent())
+                .isEqualTo(mActivity.getComponentName());
+
+        assertWithMessage("notification extras").that(notification.extras).isNotNull();
+        assertWithMessage("value of extra %s", EXTRA_TITLE)
+                .that(notification.extras.getString(EXTRA_TITLE))
+                .isEqualTo(mRealContext.getString(R.string.new_user_managed_notification_title));
+        assertWithMessage("value of extra %s", EXTRA_TEXT)
+                .that(notification.extras.getString(EXTRA_TEXT))
+                .isEqualTo(ManagedDeviceTextView.getManagedDeviceText(mRealContext));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperInvalidImportanceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperInvalidImportanceTest.java
new file mode 100644
index 0000000..de4af6d
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperInvalidImportanceTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.android.car.admin;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
+import static com.android.car.admin.NotificationHelper.newNotificationBuilder;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class NotificationHelperInvalidImportanceTest {
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    private Context mContext;
+
+    private final int mImportance;
+
+    public NotificationHelperInvalidImportanceTest(int importance) {
+        mImportance = importance;
+    }
+
+    @Test
+    public void testNewNotificationBuilder() {
+        assertThrows(IllegalArgumentException.class,
+                () -> newNotificationBuilder(mContext, mImportance));
+    }
+
+    @Parameterized.Parameters
+    public static Collection<Integer> provideParams() {
+        return Arrays.asList(
+                IMPORTANCE_LOW,
+                IMPORTANCE_MAX,
+                IMPORTANCE_MIN,
+                IMPORTANCE_NONE,
+                IMPORTANCE_UNSPECIFIED,
+                Integer.MIN_VALUE,
+                Integer.MAX_VALUE);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperTest.java
new file mode 100644
index 0000000..8610265
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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 com.android.car.admin;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+
+import static com.android.car.admin.NotificationHelper.newNotificationBuilder;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.expectThrows;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class NotificationHelperTest {
+
+    @Mock
+    private Context mContext;
+
+    @Test
+    public void testNewNotificationBuilder_nullContext() {
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> newNotificationBuilder(/* context= */ null, IMPORTANCE_HIGH));
+
+        assertWithMessage("exception message").that(exception.getMessage()).contains("context");
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
new file mode 100644
index 0000000..7997954
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 com.android.car.admin;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+
+import static com.android.car.admin.NotificationHelper.CHANNEL_ID_DEFAULT;
+import static com.android.car.admin.NotificationHelper.CHANNEL_ID_HIGH;
+import static com.android.car.admin.NotificationHelper.newNotificationBuilder;
+import static com.android.internal.R.string.android_system_label;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+@RunWith(Parameterized.class)
+public final class NotificationHelperValidImportanceTest {
+
+    private static final String SYSTEM_LABEL = "System, I am your Label!";
+    private static final String IMPORTANCE_NAME = "The name is Bond, Importance Bond!";
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private final Context mRealContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    private Context mSpiedContext;
+
+    @Mock
+    private NotificationManager mNotificationMgr;
+
+    private final int mImportance;
+    private final String mChannelId;
+    private final int mResId;
+
+    public NotificationHelperValidImportanceTest(int importance, String channelId, int resId) {
+        mImportance = importance;
+        mChannelId = channelId;
+        mResId = resId;
+    }
+
+    @Before
+    public void setContext() {
+        mSpiedContext = spy(mRealContext);
+        when(mSpiedContext.getSystemService(NotificationManager.class))
+                .thenReturn(mNotificationMgr);
+        when(mSpiedContext.getString(android_system_label)).thenReturn(SYSTEM_LABEL);
+        when(mSpiedContext.getString(mResId)).thenReturn(IMPORTANCE_NAME);
+    }
+
+    @Test
+    public void testNewNotificationBuilder() {
+        Notification.Builder builder = newNotificationBuilder(mSpiedContext, mImportance);
+        assertWithMessage("builder").that(builder).isNotNull();
+
+        // Check custom label
+        Bundle extras = builder.getExtras();
+        assertWithMessage("extras").that(extras).isNotNull();
+        String label = extras.getString(Notification.EXTRA_SUBSTITUTE_APP_NAME);
+        assertWithMessage("label (extra %s)", Notification.EXTRA_SUBSTITUTE_APP_NAME).that(label)
+                .isEqualTo(SYSTEM_LABEL);
+
+        // Check notification arguments
+        ArgumentCaptor<NotificationChannel> captor = ArgumentCaptor
+                .forClass(NotificationChannel.class);
+        verify(mNotificationMgr).createNotificationChannel(captor.capture());
+        NotificationChannel channel = captor.getValue();
+        assertWithMessage("channel id").that(channel.getId()).isEqualTo(mChannelId);
+        assertWithMessage("importance").that(channel.getImportance()).isEqualTo(mImportance);
+        assertWithMessage("name").that(channel.getName()).isEqualTo(IMPORTANCE_NAME);
+    }
+
+    @Parameterized.Parameters
+    public static List<Object[]> provideParams() {
+        return Arrays.asList(new Object[][] {
+            { IMPORTANCE_DEFAULT, CHANNEL_ID_DEFAULT, R.string.importance_default },
+            { IMPORTANCE_HIGH, CHANNEL_ID_HIGH, R.string.importance_high }
+        });
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/PerUserCarDevicePolicyServiceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/PerUserCarDevicePolicyServiceTest.java
new file mode 100644
index 0000000..796afd5
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/admin/PerUserCarDevicePolicyServiceTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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 com.android.car.admin;
+
+import static android.app.admin.DevicePolicyManager.ACTION_SHOW_NEW_USER_DISCLAIMER;
+
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_ACKED;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_RECEIVED;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_SHOWN;
+import static com.android.car.admin.PerUserCarDevicePolicyService.newUserDisclaimerStatusToString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.car.admin.PerUserCarDevicePolicyService.NewUserDisclaimerStatus;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+public final class PerUserCarDevicePolicyServiceTest extends AbstractExtendedMockitoTestCase {
+
+    @Mock
+    private Context mContext;
+
+    private PerUserCarDevicePolicyService mInstance;
+
+    @Mock
+    private DevicePolicyManager mDpm;
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+        session.spyStatic(NewUserDisclaimerActivity.class);
+    }
+
+    @Before
+    public void setFixtures() {
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDpm);
+
+        mInstance = new PerUserCarDevicePolicyService(mContext);
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED);
+    }
+
+    @Test
+    public void testGetInstance() {
+        PerUserCarDevicePolicyService instance1 = PerUserCarDevicePolicyService
+                .getInstance(mContext);
+        assertWithMessage("getInstance()#1").that(instance1).isNotNull();
+        assertWithMessage("getInstance()#1").that(instance1).isNotSameInstanceAs(mInstance);
+
+        PerUserCarDevicePolicyService instance2 = PerUserCarDevicePolicyService
+                .getInstance(mContext);
+        assertWithMessage("getInstance()#2").that(instance2).isNotNull();
+        assertWithMessage("getInstance()#2").that(instance2).isNotSameInstanceAs(mInstance);
+
+        assertWithMessage("getInstance()#2").that(instance2).isSameInstanceAs(instance1);
+        assertWithMessage("getInstance()#1").that(instance1).isSameInstanceAs(instance2);
+    }
+
+    @Test
+    public void testGetInstance_nullContext() {
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> PerUserCarDevicePolicyService.getInstance(null));
+        assertWithMessage("exception message").that(exception.getMessage()).contains("context");
+    }
+
+    @Test
+    public void testCreateAndDestroy() {
+        BroadcastReceiver receiver = callOnCreate();
+
+        callOnDestroy(receiver);
+    }
+
+    @Test
+    public void testShowWhenIntentReceived() {
+        doAnswer((inv) -> {
+            assertStatusString(NEW_USER_DISCLAIMER_STATUS_RECEIVED);
+            return null;
+        }).when(() -> NewUserDisclaimerActivity.showNotification(any()));
+        BroadcastReceiver receiver  = callOnCreate();
+
+        sendShowNewUserDisclaimerBroadcast(receiver);
+
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT);
+        verify(() -> NewUserDisclaimerActivity.showNotification(mContext));
+    }
+
+    @Test
+    public void testSetShown() {
+        mInstance.setShown();
+
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_SHOWN);
+    }
+
+    @Test
+    public void testSetAcknowledged() {
+        doNothing().when(() -> NewUserDisclaimerActivity.cancelNotification(any()));
+
+        mInstance.setAcknowledged();
+
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_ACKED);
+        verify(() -> NewUserDisclaimerActivity.cancelNotification(mContext));
+
+        verify(mDpm).resetNewUserDisclaimer();
+    }
+
+    private BroadcastReceiver callOnCreate() {
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        mInstance.onCreate();
+
+        verify(mContext).registerReceiver(captor.capture(), any());
+        BroadcastReceiver receiver = captor.getValue();
+        assertWithMessage("BroadcastReceiver captured on onCreate()").that(receiver).isNotNull();
+
+        return receiver;
+    }
+
+    private void callOnDestroy(BroadcastReceiver receiver) {
+        mInstance.onDestroy();
+
+        verify(mContext).unregisterReceiver(receiver);
+    }
+
+    private void sendShowNewUserDisclaimerBroadcast(BroadcastReceiver receiver) {
+        receiver.onReceive(mContext, new Intent(ACTION_SHOW_NEW_USER_DISCLAIMER));
+    }
+
+    private void assertStatusString(@NewUserDisclaimerStatus int expectedStatus) {
+        int actualStatus = mInstance.getNewUserDisclaimerStatus();
+        assertWithMessage("newUserDisclaimerStatus (%s=%s, %s=%s)",
+                expectedStatus, newUserDisclaimerStatusToString(expectedStatus),
+                actualStatus, newUserDisclaimerStatusToString(actualStatus))
+                        .that(actualStatus).isEqualTo(expectedStatus);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
new file mode 100644
index 0000000..4afe81e
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
@@ -0,0 +1,306 @@
+/*
+ * 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 com.android.car.cluster;
+
+import static android.car.CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_HOME;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_NONE;
+import static android.car.navigation.CarNavigationInstrumentCluster.CLUSTER_TYPE_IMAGE_CODES_ONLY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityOptions;
+import android.car.cluster.ClusterHomeManager;
+import android.car.cluster.ClusterState;
+import android.car.cluster.IClusterHomeCallback;
+import android.car.cluster.navigation.NavigationState.NavigationStateProto;
+import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.Display;
+
+import com.android.car.CarOccupantZoneService;
+import com.android.car.am.FixedActivityService;
+import com.android.car.cluster.ClusterNavigationService.ContextOwner;
+import com.android.car.hal.ClusterHalService;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterHomeServiceUnitTest {
+    private static final int CLUSTER_DISPLAY_ID = 99;
+    private static final int CLUSTER_WIDTH = 1024;
+    private static final int CLUSTER_HEIGHT = 600;
+    private static final int UI_TYPE_CLUSTER_MAPS = UI_TYPE_CLUSTER_HOME + 1;
+    private static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
+    private static final int USER_ID = 111;
+
+    private ClusterHomeService mClusterHomeService;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private ClusterHalService mClusterHalService;
+    @Mock
+    private ClusterNavigationService mNavigationService;
+    @Mock
+    private CarOccupantZoneService mOccupantZoneService;
+    @Mock
+    private FixedActivityService mFixedActivityService;
+    @Mock
+    private DisplayManager mDisplayManager;
+    @Mock
+    private Display mClusterDisplay;
+
+    private ClusterState mClusterState;
+    private int mClusterStateChanges;
+    private byte[] mNavigationState;
+
+    private IClusterHomeCallback mClusterHomeCallback;
+    private class IClusterHomeCallbackImpl extends IClusterHomeCallback.Stub {
+        @Override
+        public void onClusterStateChanged(ClusterState state, int changes) {
+            mClusterState = state;
+            mClusterStateChanges = changes;
+        }
+
+        @Override
+        public void onNavigationStateChanged(byte[] navigationState) {
+            mNavigationState = navigationState;
+        }
+    }
+
+    private ComponentName mClusterHomeActivity = new ComponentName("clusterhome.pkg", "activity");
+
+    @Before
+    public void setUp() {
+        when(mContext.getString(com.android.car.R.string.config_clusterHomeActivity))
+                .thenReturn(mClusterHomeActivity.flattenToString());
+        when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+
+        when(mOccupantZoneService.getDisplayIdForDriver(DISPLAY_TYPE_INSTRUMENT_CLUSTER))
+                .thenReturn(CLUSTER_DISPLAY_ID);
+        when(mClusterHalService.isCoreSupported()).thenReturn(true);
+        when(mClusterHalService.isNavigationStateSupported()).thenReturn(true);
+        when(mDisplayManager.getDisplay(CLUSTER_DISPLAY_ID)).thenReturn(mClusterDisplay);
+        doAnswer(invocation -> {
+            Point size = (Point) invocation.getArgument(0);
+            size.set(CLUSTER_WIDTH, CLUSTER_HEIGHT);
+            return null;
+        }).when(mClusterDisplay).getRealSize(any(Point.class));
+
+        mClusterHomeService = new ClusterHomeService(mContext, mClusterHalService,
+                mNavigationService, mOccupantZoneService, mFixedActivityService);
+        mClusterHomeService.init();
+    }
+
+    public void registerClusterHomeCallback() {
+        mClusterHomeCallback = new IClusterHomeCallbackImpl();
+        mClusterHomeService.registerCallback(mClusterHomeCallback);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mClusterHomeCallback != null) {
+            mClusterHomeService.unregisterCallback(mClusterHomeCallback);
+        }
+        mClusterHomeService.release();
+    }
+
+    @Test
+    public void initStartsClusterHomeActivity() {
+        // ClusterHomeService.init() was called in setUp().
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ActivityOptions> activityOptionsCaptor = ArgumentCaptor.forClass(
+                ActivityOptions.class);
+        verify(mFixedActivityService).startFixedActivityModeForDisplayAndUser(
+                intentCaptor.capture(), activityOptionsCaptor.capture(),
+                eq(CLUSTER_DISPLAY_ID), eq(UserHandle.USER_SYSTEM));
+        assertThat(intentCaptor.getValue().getComponent()).isEqualTo(mClusterHomeActivity);
+        assertThat(activityOptionsCaptor.getValue().getLaunchDisplayId())
+                .isEqualTo(CLUSTER_DISPLAY_ID);
+    }
+
+    @Test
+    public void getClusterStateReturnsClusterState() {
+        ClusterState clusterState = mClusterHomeService.getClusterState();
+
+        assertThat(clusterState.on).isFalse();
+        assertThat(clusterState.bounds).isEqualTo(new Rect(0, 0, CLUSTER_WIDTH, CLUSTER_HEIGHT));
+        assertThat(clusterState.insets).isEqualTo(Insets.NONE);
+        assertThat(clusterState.uiType).isEqualTo(UI_TYPE_CLUSTER_HOME);
+        assertThat(clusterState.displayId).isEqualTo(CLUSTER_DISPLAY_ID);
+    }
+
+    @Test
+    public void onSwitchUiSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        mClusterHomeService.onSwitchUi(UI_TYPE_CLUSTER_MAPS);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_UI_TYPE);
+        assertThat(mClusterState.uiType).isEqualTo(UI_TYPE_CLUSTER_MAPS);
+        assertThat(mClusterHomeService.getClusterState().uiType).isEqualTo(UI_TYPE_CLUSTER_MAPS);
+    }
+
+    @Test
+    public void displayOnSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        mClusterHomeService.onDisplayState(ClusterHalService.DISPLAY_ON,
+                /* bounds= */ null, /* insets= */ null);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_DISPLAY_ON_OFF);
+        assertThat(mClusterState.on).isTrue();
+        assertThat(mClusterHomeService.getClusterState().on).isTrue();
+    }
+
+    @Test
+    public void displayBoundsSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        Rect newBounds = new Rect(10, 10, CLUSTER_WIDTH - 10, CLUSTER_HEIGHT - 10);
+        mClusterHomeService.onDisplayState(ClusterHalService.DONT_CARE,
+                newBounds, /* insets= */ null);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_DISPLAY_BOUNDS);
+        assertThat(mClusterState.bounds).isEqualTo(newBounds);
+        assertThat(mClusterHomeService.getClusterState().bounds).isEqualTo(newBounds);
+    }
+
+    @Test
+    public void displayInsetsSendsDisplayState() {
+        registerClusterHomeCallback();
+
+        Insets newInsets = Insets.of(10, 10, 10, 10);
+        mClusterHomeService.onDisplayState(ClusterHalService.DONT_CARE, /* bounds= */ null,
+                newInsets);
+
+        // mClusterState and mClusterStateChanges are updated through mClusterHomeCallback.
+        assertThat(mClusterStateChanges).isEqualTo(ClusterHomeManager.CONFIG_DISPLAY_INSETS);
+        assertThat(mClusterState.insets).isEqualTo(newInsets);
+        assertThat(mClusterHomeService.getClusterState().insets).isEqualTo(newInsets);
+    }
+
+    @Test
+    public void onNavigationStateChangedSendsNavigationState() {
+        registerClusterHomeCallback();
+
+        Bundle bundle = new Bundle();
+        byte[] newNavState = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+        bundle.putByteArray(NAV_STATE_PROTO_BUNDLE_KEY, newNavState);
+        mClusterHomeService.onNavigationStateChanged(bundle);
+
+        // mNavigationState is updated through mClusterHomeCallback.
+        assertThat(mNavigationState).isEqualTo(newNavState);
+
+        ArgumentCaptor<byte[]> navStateCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mClusterHalService).sendNavigationState(navStateCaptor.capture());
+        assertThat(navStateCaptor.getValue()).isEqualTo(newNavState);
+    }
+
+    @Test
+    public void getInstrumentClusterInfoReturnsClusterInfo() {
+        CarNavigationInstrumentCluster clusterInfo = mClusterHomeService.getInstrumentClusterInfo();
+
+        assertThat(clusterInfo.getType()).isEqualTo(CLUSTER_TYPE_IMAGE_CODES_ONLY);
+    }
+
+    @Test
+    public void notifyNavContextOwnerChangedSendsNavigationState() throws
+            InvalidProtocolBufferException {
+        ContextOwner owner = new ContextOwner(/* uid= */ 123, /* pid= */ 456);
+        mClusterHomeService.notifyNavContextOwnerChanged(owner);
+
+        ArgumentCaptor<byte[]> navStateCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mClusterHalService).sendNavigationState(navStateCaptor.capture());
+        NavigationStateProto navState = NavigationStateProto.parseFrom(navStateCaptor.getValue());
+        assertThat(navState.getServiceStatus())
+                .isEqualTo(NavigationStateProto.ServiceStatus.NORMAL);
+    }
+
+    @Test
+    public void reportStateInvokesHal() {
+        byte[] uiAvailability = new byte[] {(byte) 1, (byte) 1, (byte) 0, (byte) 1};
+        mClusterHomeService.reportState(UI_TYPE_CLUSTER_MAPS, UI_TYPE_CLUSTER_NONE, uiAvailability);
+
+        ArgumentCaptor<byte[]> uiAvailabilityCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mClusterHalService).reportState(/* onOff= */ anyInt(), /* bounds= */ any(Rect.class),
+                any(Insets.class), eq(UI_TYPE_CLUSTER_MAPS), eq(UI_TYPE_CLUSTER_NONE),
+                uiAvailabilityCaptor.capture());
+        assertThat(uiAvailabilityCaptor.getValue()).isEqualTo(uiAvailability);
+    }
+
+    @Test
+    public void requestDisplayInvokesHal() {
+        mClusterHomeService.requestDisplay(UI_TYPE_CLUSTER_MAPS);
+
+        verify(mClusterHalService).requestDisplay(eq(UI_TYPE_CLUSTER_MAPS));
+    }
+
+    @Test
+    public void startFixedActivityModeAsUserInvokesFixedActivityService() {
+        Intent intent = Intent.makeMainActivity(
+                ComponentName.createRelative("test.pkg", "testClusterActivity"));
+        ActivityOptions activityOptions = ActivityOptions.makeBasic();
+        mClusterHomeService.startFixedActivityModeAsUser(
+                intent, activityOptions.toBundle(), USER_ID);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ActivityOptions> activityOptionsCaptor = ArgumentCaptor.forClass(
+                ActivityOptions.class);
+        verify(mFixedActivityService).startFixedActivityModeForDisplayAndUser(
+                intentCaptor.capture(), activityOptionsCaptor.capture(),
+                eq(CLUSTER_DISPLAY_ID), eq(USER_ID));
+        assertThat(intentCaptor.getValue()).isEqualTo(intent);
+        assertThat(activityOptionsCaptor.getValue().getLaunchDisplayId()).isEqualTo(
+                CLUSTER_DISPLAY_ID);
+    }
+
+    @Test
+    public void stopFixedActivityModeInvokesFixedActivityService() {
+        mClusterHomeService.stopFixedActivityMode();
+
+        verify(mFixedActivityService).stopFixedActivityMode(eq(CLUSTER_DISPLAY_ID));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
index e128b19..814ccba 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.car.hal;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.car.Car;
 import android.car.VehicleHvacFanDirection;
 import android.car.VehiclePropertyIds;
@@ -29,10 +32,9 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.car.internal.PropertyPermissionMapping;
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 
-import com.google.common.truth.Truth;
-
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -50,7 +52,7 @@
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     private PropertyHalServiceIds mPropertyHalServiceIds;
-
+    private PropertyPermissionMapping mPermissionMapping;
     private static final String TAG = PropertyHalServiceIdsTest.class.getSimpleName();
     private static final int VENDOR_PROPERTY_1 = 0x21e01111;
     private static final int VENDOR_PROPERTY_2 = 0x21e01112;
@@ -92,6 +94,7 @@
     @Before
     public void setUp() {
         mPropertyHalServiceIds = new PropertyHalServiceIds();
+        mPermissionMapping = new PropertyPermissionMapping();
         // set up read permission and write permission to VENDOR_PROPERTY_1
         CONFIG_ARRAY.add(VENDOR_PROPERTY_1);
         CONFIG_ARRAY.add(VehicleVendorPermission.PERMISSION_DEFAULT);
@@ -117,14 +120,18 @@
      */
     @Test
     public void checkPermissionForSystemProperty() {
-        Assert.assertEquals(Car.PERMISSION_CAR_ENGINE_DETAILED,
-                mPropertyHalServiceIds.getReadPermission(VehiclePropertyIds.ENGINE_OIL_LEVEL));
-        Assert.assertNull(
-                mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.ENGINE_OIL_LEVEL));
-        Assert.assertEquals(Car.PERMISSION_CONTROL_CAR_CLIMATE,
-                mPropertyHalServiceIds.getReadPermission(VehiclePropertyIds.HVAC_FAN_SPEED));
-        Assert.assertEquals(Car.PERMISSION_CONTROL_CAR_CLIMATE,
-                mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.HVAC_FAN_SPEED));
+        assertThat(mPropertyHalServiceIds.getReadPermission(VehiclePropertyIds.ENGINE_OIL_LEVEL))
+                .isEqualTo(Car.PERMISSION_CAR_ENGINE_DETAILED);
+        assertThat(mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.ENGINE_OIL_LEVEL))
+                .isNull();
+        assertThat(mPropertyHalServiceIds.getReadPermission(VehiclePropertyIds.HVAC_FAN_SPEED))
+                .isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE);
+        assertThat(mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.HVAC_FAN_SPEED))
+                .isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE);
+        assertThat(mPermissionMapping.getReadPermission(VehiclePropertyIds.HVAC_FAN_SPEED))
+                .isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE);
+        assertThat(mPermissionMapping.getWritePermission(VehiclePropertyIds.HVAC_FAN_SPEED))
+                .isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE);
     }
     /**
      * Test {@link PropertyHalServiceIds#customizeVendorPermission(List)}
@@ -133,28 +140,31 @@
     public void checkPermissionForVendorProperty() {
         // test insert a valid config
         mPropertyHalServiceIds.customizeVendorPermission(CONFIG_ARRAY);
+        assertThat(mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_1))
+                .isEqualTo(Car.PERMISSION_VENDOR_EXTENSION);
+        assertThat(mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_1)).isNull();
 
-        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
-                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_1));
-        Assert.assertNull(mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_1));
+        assertThat(mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_2))
+                .isEqualTo(android.car.hardware.property
+                        .VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE);
+        assertThat(mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_2))
+                .isEqualTo(android.car.hardware.property
+                        .VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE);
 
-        Assert.assertEquals(android.car.hardware.property
-                        .VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE,
-                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_2));
-        Assert.assertEquals(android.car.hardware.property
-                        .VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE,
-                mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_2));
+        assertThat(mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_3))
+                .isEqualTo(android.car.hardware.property
+                        .VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO);
+        assertThat(mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_3))
+                .isEqualTo(Car.PERMISSION_VENDOR_EXTENSION);
 
-        Assert.assertEquals(android.car.hardware.property
-                        .VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO,
-                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_3));
-        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
-                mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_3));
-
-        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
-                mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_4));
-        Assert.assertEquals(Car.PERMISSION_VENDOR_EXTENSION,
-                mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_4));
+        assertThat(mPropertyHalServiceIds.getReadPermission(VENDOR_PROPERTY_4))
+                .isEqualTo(Car.PERMISSION_VENDOR_EXTENSION);
+        assertThat(mPropertyHalServiceIds.getWritePermission(VENDOR_PROPERTY_4))
+                .isEqualTo(Car.PERMISSION_VENDOR_EXTENSION);
+        assertThat(mPermissionMapping.getReadPermission(VENDOR_PROPERTY_4))
+                .isEqualTo(Car.PERMISSION_VENDOR_EXTENSION);
+        assertThat(mPermissionMapping.getWritePermission(VENDOR_PROPERTY_4))
+                .isEqualTo(Car.PERMISSION_VENDOR_EXTENSION);
 
         // test insert invalid config
         try {
@@ -171,10 +181,12 @@
     @Test
     public void checkVendorPropertyId() {
         for (int vendorProp : VENDOR_PROPERTY_IDS) {
-            Assert.assertTrue(mPropertyHalServiceIds.isSupportedProperty(vendorProp));
+            assertWithMessage("Property does not exist.").that(
+                    mPropertyHalServiceIds.isSupportedProperty(vendorProp)).isTrue();
         }
         for (int systemProp : SYSTEM_PROPERTY_IDS) {
-            Assert.assertTrue(mPropertyHalServiceIds.isSupportedProperty(systemProp));
+            assertWithMessage("Property does not exist.").that(
+                    mPropertyHalServiceIds.isSupportedProperty(systemProp)).isTrue();
         }
     }
 
@@ -183,14 +195,11 @@
      */
     @Test
     public void testPayload() {
-        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_VALID_VALUE)).isTrue();
-        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_EXTRA_VALUE)).isFalse();
-        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_VALUE)).isFalse();
-        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_TYPE_VALUE))
-                .isFalse();
-
-        Truth.assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_VALID)).isTrue();
-        Truth.assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_INVALID))
-                .isFalse();
+        assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_VALID_VALUE)).isTrue();
+        assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_EXTRA_VALUE)).isFalse();
+        assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_VALUE)).isFalse();
+        assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_TYPE_VALUE)).isFalse();
+        assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_VALID)).isTrue();
+        assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_INVALID)).isFalse();
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
index 6384d69..1ad6b7e 100644
--- a/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
@@ -560,6 +560,29 @@
         assertThat(listenerAudio.getCurrentPowerPolicy()).isNull();
     }
 
+    /**
+     * This test case increases the code coverage to cover methods
+     * {@code describeContents()} and {@code newArray()}. They are public APIs
+     * can not be marked out as BOILERPLATE_CODE.
+     */
+    @Test
+    public void testParcelableCreation() throws Exception {
+        grantPowerPolicyPermission();
+
+        CarPowerPolicy policy = mService.getCurrentPowerPolicy();
+        assertThat(policy.describeContents()).isEqualTo(0);
+
+        CarPowerPolicy[] policies = CarPowerPolicy.CREATOR.newArray(1);
+        assertThat(policies.length).isEqualTo(1);
+
+        CarPowerPolicyFilter filterAudio = new CarPowerPolicyFilter.Builder()
+                .setComponents(PowerComponent.AUDIO).build();
+        assertThat(filterAudio.describeContents()).isEqualTo(0);
+
+        CarPowerPolicyFilter[] filters = CarPowerPolicyFilter.CREATOR.newArray(1);
+        assertThat(filters.length).isEqualTo(1);
+    }
+
     private void suspendAndResume() throws Exception {
         Log.d(TAG, "suspend()");
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
new file mode 100644
index 0000000..0e9afa2
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.android.car.telemetry.databroker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import com.android.car.telemetry.TelemetryProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DataBrokerUnitTest {
+    private final DataBrokerImpl mDataBroker = new DataBrokerImpl(new ScriptResultListenerImpl());
+    private static final TelemetryProto.VehiclePropertyPublisher
+            VEHICLE_PROPERTY_PUBLISHER_CONFIGURATION =
+            TelemetryProto.VehiclePropertyPublisher.newBuilder().setReadRate(
+                    1).setVehiclePropertyId(1000).build();
+    private static final TelemetryProto.Publisher PUBLISHER_CONFIGURATION =
+            TelemetryProto.Publisher.newBuilder().setVehicleProperty(
+                    VEHICLE_PROPERTY_PUBLISHER_CONFIGURATION).build();
+    private static final TelemetryProto.Subscriber SUBSCRIBER_FOO =
+            TelemetryProto.Subscriber.newBuilder().setHandler("function_name_foo").setPublisher(
+                    PUBLISHER_CONFIGURATION).build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_FOO =
+            TelemetryProto.MetricsConfig.newBuilder().setName("Foo").setVersion(
+                    1).addSubscribers(SUBSCRIBER_FOO).build();
+    private static final TelemetryProto.Subscriber SUBSCRIBER_BAR =
+            TelemetryProto.Subscriber.newBuilder().setHandler("function_name_bar").setPublisher(
+                    PUBLISHER_CONFIGURATION).build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_BAR =
+            TelemetryProto.MetricsConfig.newBuilder().setName("Bar").setVersion(
+                    1).addSubscribers(SUBSCRIBER_BAR).build();
+
+    @Test
+    public void testAddMetricsConfiguration_newMetricsConfig() {
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        assertThat(mDataBroker.getPublisherMap().containsKey(
+                TelemetryProto.Publisher.PublisherCase.VEHICLE_PROPERTY)).isTrue();
+        assertThat(mDataBroker.getSubscriptionMap()).containsKey(METRICS_CONFIG_FOO.getName());
+        // there should be one data subscriber in the subscription list of METRICS_CONFIG_FOO
+        assertThat(mDataBroker.getSubscriptionMap().get(METRICS_CONFIG_FOO.getName())).hasSize(1);
+    }
+
+    @Test
+    public void testAddMetricsConfiguration_multipleMetricsConfigsSamePublisher() {
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_BAR);
+
+        assertThat(mDataBroker.getPublisherMap()).hasSize(1);
+        assertThat(mDataBroker.getSubscriptionMap()).containsKey(METRICS_CONFIG_FOO.getName());
+        assertThat(mDataBroker.getSubscriptionMap()).containsKey(METRICS_CONFIG_BAR.getName());
+    }
+
+    @Test
+    public void testAddMetricsConfiguration_addSameMetricsConfigs() {
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        boolean status = mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        assertThat(status).isFalse();
+    }
+
+    @Test
+    public void testRemoveMetricsConfiguration_publisherShouldExist() {
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        assertThat(mDataBroker.getPublisherMap()).containsKey(
+                TelemetryProto.Publisher.PublisherCase.VEHICLE_PROPERTY);
+        assertThat(mDataBroker.getSubscriptionMap()).doesNotContainKey(
+                METRICS_CONFIG_FOO.getName());
+    }
+
+    @Test
+    public void testRemoveMetricsConfiguration_removeNonexistentMetricsConfig() {
+        boolean status = mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        assertThat(status).isFalse();
+    }
+
+    private static class ScriptResultListenerImpl implements DataBroker.ScriptResultListener {
+        @Override
+        public void onScriptResult(Bundle scriptResult) {
+            // nothing to do
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java
index a8b1362..1828c01 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java
@@ -40,6 +40,7 @@
 import android.car.watchdog.ICarWatchdogService;
 import android.car.watchdog.ICarWatchdogServiceCallback;
 import android.car.watchdog.IResourceOveruseListener;
+import android.car.watchdog.IoOveruseAlertThreshold;
 import android.car.watchdog.IoOveruseConfiguration;
 import android.car.watchdog.IoOveruseStats;
 import android.car.watchdog.PackageKillableState;
@@ -68,6 +69,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -476,6 +478,80 @@
                 .isEqualTo(null);
     }
 
+    @Test
+    public void testIoOveruseAlertThresholdToStringAndDescribeContent() {
+        IoOveruseAlertThreshold ioOveruseAlertThreshold = new IoOveruseAlertThreshold(1, 2);
+        int content = ioOveruseAlertThreshold.describeContents();
+        String string = ioOveruseAlertThreshold.toString();
+
+        assertThat(content).isEqualTo(0);
+        assertThat(string).isEqualTo(
+                "IoOveruseAlertThreshold { durationInSeconds = 1, writtenBytesPerSecond = 2 }");
+    }
+
+    @Test
+    public void testIoOveruseConfigurationToStringAndDescribeContent() {
+        PerStateBytes expectedPerStateBytes = new PerStateBytes(6666666, 7777777, 8888888);
+        IoOveruseConfiguration ioOveruseConfiguration = new IoOveruseConfiguration.Builder(
+                expectedPerStateBytes,
+                new HashMap<>(), new HashMap<>(), new ArrayList<>()).build();
+        int content = ioOveruseConfiguration.describeContents();
+        String string = ioOveruseConfiguration.toString();
+
+        assertThat(content).isEqualTo(0);
+        assertThat(string)
+                .isEqualTo("IoOveruseConfiguration { componentLevelThresholds = "
+                        + expectedPerStateBytes
+                        + ", packageSpecificThresholds = {}, appCategorySpecificThresholds = {}, "
+                        + "systemWideThresholds = [] }");
+    }
+
+    @Test
+    public void testIoOveruseStatsDescribeContent() {
+        IoOveruseStats ioOveruseStats =
+                new IoOveruseStats.Builder(0, 10).setStartTime(1).setDurationInSeconds(5).build();
+        int content = ioOveruseStats.describeContents();
+
+        assertThat(content).isEqualTo(0);
+    }
+
+    @Test
+    public void testPackageKillableStateToStringAndDescribeContent() {
+        PackageKillableState packageKillableState = new PackageKillableState("packageName", 10, 1);
+        int content = packageKillableState.describeContents();
+        String string = packageKillableState.toString();
+
+        assertThat(content).isEqualTo(0);
+        assertThat(string).isEqualTo(
+                "PackageKillableState { packageName = packageName, userId = 10, "
+                        + "killableState = "
+                        + PackageKillableState.killableStateToString(1)
+                        + " }");
+    }
+
+    @Test
+    public void testResourceOveruseConfigurationToStringAndDescribeContent() {
+        ResourceOveruseConfiguration resourceOveruseConfiguration =
+                new ResourceOveruseConfiguration.Builder(
+                    ResourceOveruseConfiguration.COMPONENT_TYPE_SYSTEM, new ArrayList<>(),
+                    new ArrayList<>(), new HashMap<>())
+                .addPackagesToAppCategoryTypes("key", "value")
+                .addSafeToKillPackages("safeToKillApp")
+                .addVendorPackagePrefixes("vendorPackagePrefix")
+                .build();
+        int content = resourceOveruseConfiguration.describeContents();
+        String string = resourceOveruseConfiguration.toString();
+
+        assertThat(content).isEqualTo(0);
+        assertThat(string)
+                .isEqualTo("ResourceOveruseConfiguration { componentType = "
+                        + ResourceOveruseConfiguration.componentTypeToString(
+                                ResourceOveruseConfiguration.COMPONENT_TYPE_SYSTEM)
+                        + ", safeToKillPackages = [safeToKillApp], vendorPackagePrefixes = "
+                        + "[vendorPackagePrefix], packagesToAppCategoryTypes = {key=value}, "
+                        + "ioOveruseConfiguration = null }");
+    }
+
     private ICarWatchdogServiceCallback registerClient(
             CarWatchdogManager.CarWatchdogClientCallback client) throws Exception {
         mCarWatchdogManager.registerClient(mExecutor, client, TIMEOUT_CRITICAL);